diff --git a/frontend/src/uxbox/main/data/auth.cljs b/frontend/src/uxbox/main/data/auth.cljs index 1450dd6b9..591d1513d 100644 --- a/frontend/src/uxbox/main/data/auth.cljs +++ b/frontend/src/uxbox/main/data/auth.cljs @@ -6,7 +6,7 @@ (ns uxbox.main.data.auth (:require - [struct.alpha :as st] + [cljs.spec.alpha :as s] [beicon.core :as rx] [potok.core :as ptk] [uxbox.main.repo :as rp] @@ -14,9 +14,15 @@ [uxbox.main.data.users :as du] [uxbox.util.messages :as um] [uxbox.util.router :as rt] + [uxbox.util.spec :as us] [uxbox.util.i18n :as i18n :refer [tr]] [uxbox.util.storage :refer [storage]])) +(s/def ::username string?) +(s/def ::password string?) +(s/def ::fullname string?) +(s/def ::email ::us/email) + ;; --- Logged In ;; TODO: add spec @@ -43,13 +49,12 @@ ;; --- Login -(st/defs ::login - (st/dict :username ::st/string - :password ::st/string)) +(s/def ::login-params + (s/keys :req-un [::username ::password])) (defn login [{:keys [username password] :as data}] - (assert (st/valid? ::login data)) + (s/assert ::login-params data) (reify ptk/UpdateEvent (update [_ state] @@ -93,17 +98,17 @@ ;; --- Register -(st/defs ::register - (st/dict :fullname ::st/string - :username ::st/string - :password ::st/string - :email ::st/email)) +(s/def ::register-params + (s/keys :req-un [::fullname + ::username + ::password + ::email])) (defn register "Create a register event instance." [data on-error] - (assert (st/valid? ::register data)) - (assert (fn? on-error)) + (s/assert ::register-params data) + (s/assert ::us/fn on-error) (reify ptk/WatchEvent (watch [_ state stream] @@ -122,12 +127,12 @@ ;; --- Recovery Request -(st/defs ::recovery-request - (st/dict :username ::st/string)) +(s/def ::recovery-request-params + (s/keys :req-un [::username])) (defn recovery-request [data] - (assert (st/valid? ::recovery-request data)) + (s/assert ::recovery-request-params data) (reify ptk/WatchEvent (watch [_ state stream] @@ -164,13 +169,13 @@ ;; --- Recovery (Password) -(st/defs ::recovery - (st/dict :username ::st/string - :token ::st/string)) +(s/def ::token string?) +(s/def ::recovery-params + (s/keys :req-un [::username ::token])) (defn recovery [{:keys [token password] :as data}] - (assert (st/valid? ::recovery data)) + (s/assert ::recovery-params data) (reify ptk/WatchEvent (watch [_ state stream] diff --git a/frontend/src/uxbox/main/data/pages.cljs b/frontend/src/uxbox/main/data/pages.cljs index af429d42f..8c85f7cf7 100644 --- a/frontend/src/uxbox/main/data/pages.cljs +++ b/frontend/src/uxbox/main/data/pages.cljs @@ -6,10 +6,10 @@ (ns uxbox.main.data.pages (:require + [cljs.spec.alpha :as s] [beicon.core :as rx] [cuerdas.core :as str] [potok.core :as ptk] - [struct.alpha :as st] [uxbox.main.repo :as rp] [uxbox.util.data :refer [index-by-id]] [uxbox.util.spec :as us] @@ -18,54 +18,65 @@ ;; --- Struct -(st/defs ::inst inst?) -(st/defs ::width (st/&& ::st/number ::st/positive)) -(st/defs ::height (st/&& ::st/number ::st/positive)) +(s/def ::id ::us/uuid) +(s/def ::name ::us/string) +(s/def ::inst ::us/inst) +(s/def ::type ::us/keyword) +(s/def ::project ::us/uuid) +(s/def ::created-at ::us/inst) +(s/def ::modified-at ::us/inst) +(s/def ::version ::us/number) +(s/def ::width (s/and ::us/number ::us/positive)) +(s/def ::height (s/and ::us/number ::us/positive)) +(s/def ::grid-x-axis ::us/number) +(s/def ::grid-y-axis ::us/number) +(s/def ::grid-color ::us/string) +(s/def ::order ::us/number) +(s/def ::background ::us/string) +(s/def ::background-opacity ::us/number) +(s/def ::user ::us/uuid) -(st/defs ::metadata - (st/dict :width ::width - :height ::height - :grid-y-axis (st/opt ::st/number) - :grid-x-axis (st/opt ::st/number) - :grid-color (st/opt ::st/string) - :order (st/opt ::st/number) - :background (st/opt ::st/string) - :background-opacity (st/opt ::st/number))) +(s/def ::metadata + (s/keys :req-un [::width ::height] + :opt-un [::grid-y-axis + ::grid-x-axis + ::grid-color + ::order + ::background + ::background-opacity])) -(st/defs ::shapes-list - (st/coll-of ::st/uuid)) +(s/def ::shapes + (s/coll-of ::us/uuid :kind vector? :into [])) -(st/defs ::page-entity - (st/dict :id ::st/uuid - :name ::st/string - :project ::st/uuid - :created-at ::inst - :modified-at ::inst - :user ::st/uuid - :metadata ::metadata - :shapes ::shapes-list)) +(s/def ::page-entity + (s/keys :req-un [::id + ::name + ::project + ::created-at + ::modified-at + ::user + ::metadata + ::shapes])) -(st/defs ::minimal-shape - (st/dict :id ::st/uuid - :type ::st/keyword - :name ::st/string)) +(s/def ::minimal-shape + (s/keys :req-un [::type ::name] + :opt-un [::id])) -(st/defs ::server-page-data-sapes - (st/coll-of ::minimal-shape)) +(s/def :uxbox.backend/shapes + (s/coll-of ::minimal-shape :kind vector?)) -(st/defs ::server-page-data - (st/dict :shapes ::server-page-data-sapes)) +(s/def :uxbox.backend/data + (s/keys :req-un [:uxbox.backend/shapes])) -(st/defs ::server-page - (st/dict :id ::st/uuid - :name ::st/string - :project ::st/uuid - :version ::st/integer - :created-at ::inst - :modified-at ::inst - :user ::st/uuid - :metadata ::metadata - :data ::server-page-data)) +(s/def ::server-page + (s/keys :req-un [::id ::name + ::project + ::version + ::created-at + ::modified-at + ::user + ::metadata + :uxbox.backend/data])) ;; --- Protocols @@ -173,15 +184,12 @@ (declare rehash-pages) -(st/defs ::page-created - (st/dict :id ::st/uuid - :name ::st/string - :project ::st/uuid - :metadata ::metadata)) +(s/def ::page-created-params + (s/keys :req-un [::id ::name ::project ::metadata])) (defn page-created [data] - (assert (st/valid? ::page-created data) "invalid parameters") + (s/assert ::page-created-event data) (reify ptk/UpdateEvent (update [_ state] @@ -197,15 +205,12 @@ ;; --- Create Page -(st/defs ::create-page - (st/dict :name ::st/string - :project ::st/uuid - :width ::width - :height ::height)) +(s/def ::created-page-params + (s/keys :req-un [::name ::project ::width ::height])) (defn create-page [{:keys [name project width height layout] :as data}] - (assert (st/valid? ::create-page data)) + (s/assert ::created-page-params data) (reify ptk/WatchEvent (watch [this state s] @@ -231,7 +236,7 @@ (defn page-persisted [data] - (assert (st/valid? ::server-page data)) + (s/assert ::server-page data) (reify cljs.core/IDeref (-deref [_] data) @@ -278,24 +283,24 @@ ;; --- Page Metadata Persisted -(deftype MetadataPersisted [id data] - ptk/UpdateEvent - (update [_ state] - (assoc-in state [:pages id :version] (:version data)))) - -(st/defs ::version integer?) -(st/defs ::metadata-persisted-event - (st/dict :id ::st/uuid - :version ::version)) - -(defn metadata-persisted? - [v] - (instance? MetadataPersisted v)) +(s/def ::metadata-persisted-params + (s/keys :req-un [::id ::version])) (defn metadata-persisted [{:keys [id] :as data}] - {:pre [(st/valid? ::metadata-persisted-event data)]} - (MetadataPersisted. id data)) + (s/assert ::metadata-persisted-params data) + (reify + ptk/EventType + (type [_] ::metadata-persisted) + + ptk/UpdateEvent + (update [_ state] + (assoc-in state [:pages id :version] (:version data))))) + +(defn metadata-persisted? + [v] + (= ::metadata-persisted (ptk/type v))) + ;; --- Persist Page Metadata @@ -316,29 +321,28 @@ ;; --- Update Page -(deftype UpdatePage [id data] - IPageUpdate - ptk/UpdateEvent - (update [_ state] - (update-in state [:pages id] merge (dissoc data :id :version)))) - (defn update-page [id data] - {:pre [(uuid? id) (st/valid? ::page-entity data)]} - (UpdatePage. id data)) + (s/assert ::page-entity data) + (s/assert ::id id) + (reify + IPageUpdate + ptk/UpdateEvent + (update [_ state] + (update-in state [:pages id] merge (dissoc data :id :version))))) ;; --- Update Page Metadata -(deftype UpdateMetadata [id metadata] - IMetadataUpdate - ptk/UpdateEvent - (update [this state] - (assoc-in state [:pages id :metadata] metadata))) - (defn update-metadata [id metadata] - {:pre [(uuid? id) (st/valid? ::metadata metadata)]} - (UpdateMetadata. id metadata)) + (s/assert ::id id) + (s/assert ::metadata metadata) + (reify + IMetadataUpdate + ptk/UpdateEvent + (update [this state] + (assoc-in state [:pages id :metadata] metadata)))) + ;; --- Rehash Pages ;; @@ -382,24 +386,21 @@ ;; A specialized event for persist data ;; from the update page form. -(st/defs ::persist-page-update-form - (st/dict :id ::st/uuid - :name ::st/string - :width ::width - :height ::height)) +(s/def ::persist-page-update-form-params + (s/keys :req-un [::id ::name ::width ::height])) (defn persist-page-update-form [{:keys [id name width height] :as data}] - (assert (st/valid? ::persist-page-update-form data)) + (s/assert ::persist-page-update-form-params data) (reify - ptk/WatchEvent - (watch [_ state stream] - (let [page (-> (get-in state [:pages id]) - (assoc-in [:name] name) - (assoc-in [:metadata :width] width) - (assoc-in [:metadata :height] height))] - (rx/of (update-page id page)))))) - + ptk/UpdateEvent + (update [_ state] + (update-in state [:pages id] + (fn [page] + (-> (assoc page :name name) + (assoc-in [:name] name) + (assoc-in [:metadata :width] width) + (assoc-in [:metadata :height] height))))))) ;; --- Delete Page (by id) diff --git a/frontend/src/uxbox/main/data/projects.cljs b/frontend/src/uxbox/main/data/projects.cljs index 7c4edfa19..9d4bb03a3 100644 --- a/frontend/src/uxbox/main/data/projects.cljs +++ b/frontend/src/uxbox/main/data/projects.cljs @@ -10,7 +10,6 @@ [cuerdas.core :as str] [beicon.core :as rx] [potok.core :as ptk] - [struct.core :as st] [uxbox.main.repo :as rp] [uxbox.main.data.pages :as udp] [uxbox.util.uuid :as uuid] @@ -35,21 +34,12 @@ ::created-at ::modified-at])) -(st/defs project-spec - {:id [st/required st/uuid] - :name [st/required st/string] - :version [st/required st/integer] - :user [st/required st/uuid] - :created-at [st/required inst?] - :modified-at [st/required inst?]}) - ;; --- Helpers (defn assoc-project "A reduce function for assoc the project to the state map." [state {:keys [id] :as project}] - (assert (st/valid? project-spec project) - "invalid project instance") + (s/assert ::project-entity project) (update-in state [:projects id] merge project)) (defn dissoc-project @@ -170,15 +160,12 @@ ;; --- Create Project -(st/defs create-project-spec - {:name [st/required st/string] - :width [st/required st/number st/positive] - :height [st/required st/number st/positive]}) +(s/def ::create-project-params + (s/keys :req-un [::name ::width ::height])) (defn create-project [{:keys [name] :as params}] - (assert (st/valid? create-project-spec params) - "invalid params for create project event") + (s/assert ::create-project-params params) (reify ptk/WatchEvent (watch [this state stream] diff --git a/frontend/src/uxbox/main/data/shapes.cljs b/frontend/src/uxbox/main/data/shapes.cljs index 154bdf6c2..681ceef1e 100644 --- a/frontend/src/uxbox/main/data/shapes.cljs +++ b/frontend/src/uxbox/main/data/shapes.cljs @@ -5,14 +5,17 @@ ;; Copyright (c) 2015-2019 Andrey Antukh (ns uxbox.main.data.shapes - (:require [cljs.spec.alpha :as s] - [uxbox.main.geom :as geom] - [uxbox.util.geom.matrix :as gmt] - [uxbox.util.uuid :as uuid] - [uxbox.util.data :refer [index-of]])) + (:require + [cljs.spec.alpha :as s] + [uxbox.main.geom :as geom] + [uxbox.util.data :refer [index-of]] + [uxbox.util.geom.matrix :as gmt] + [uxbox.util.spec :as us] + [uxbox.util.uuid :as uuid])) ;; --- Specs +(s/def ::id ::us/uuid) (s/def ::blocked boolean?) (s/def ::collapsed boolean?) (s/def ::content string?) @@ -49,7 +52,7 @@ (s/def ::attributes (s/keys :opt-un [::blocked ::collapsed - ::conent + ::content ::fill-color ::fill-opacity ::font-family @@ -72,10 +75,10 @@ ::y1 ::y2])) (s/def ::minimal-shape - (s/keys ::req-un [::id ::page ::type ::name])) + (s/keys :req-un [::id ::page ::type ::name])) (s/def ::shape - (s/merge ::minimal-shape ::attributes)) + (s/and ::minimal-shape ::attributes)) (s/def ::rect-like-shape (s/keys :req-un [::x1 ::y1 ::x2 ::y2 ::type])) diff --git a/frontend/src/uxbox/main/data/users.cljs b/frontend/src/uxbox/main/data/users.cljs index a00abc355..93919375f 100644 --- a/frontend/src/uxbox/main/data/users.cljs +++ b/frontend/src/uxbox/main/data/users.cljs @@ -6,8 +6,8 @@ (ns uxbox.main.data.users (:require + [cljs.spec.alpha :as s] [beicon.core :as rx] - [struct.core :as s] [potok.core :as ptk] [uxbox.main.repo :as rp] [uxbox.util.i18n :as i18n :refer [tr]] @@ -58,17 +58,22 @@ ;; --- Update Profile -(s/defs update-profile-spec - {:fullname [s/required s/string] - :email [s/required s/email] - :username [s/required s/string] - :language [s/required s/string]}) +(s/def ::fullname string?) +(s/def ::email ::us/email) +(s/def ::password string?) +(s/def ::language string?) + +(s/def ::update-profile-params + (s/keys :req-un [::fullname + ::email + ::username + ::language])) (defn update-profile [data {:keys [on-success on-error]}] - {:pre [(s/valid? update-profile-spec data) - (fn? on-error) - (fn? on-success)]} + (s/assert ::update-profile-params data) + (s/assert ::us/fn on-error) + (s/assert ::us/fn on-success) (reify ptk/WatchEvent (watch [_ state s] @@ -89,16 +94,20 @@ ;; --- Update Password (Form) -(s/defs update-password-spec - {:password-1 [s/required s/string] - :password-2 [s/required s/string [s/identical-to :password-1]] - :password-old [s/required s/string]}) +(s/def ::password-1 string?) +(s/def ::password-2 string?) +(s/def ::password-old string?) + +(s/def ::update-password-params + (s/keys :req-un [::password-1 + ::password-2 + ::password-old])) (defn update-password [data {:keys [on-success on-error]}] - {:pre [(s/valid? update-password-spec data) - (fn? on-success) - (fn? on-error)]} + (s/assert ::update-password-params data) + (s/assert ::us/fn on-success) + (s/assert ::us/fn on-error) (reify ptk/WatchEvent (watch [_ state s] diff --git a/frontend/src/uxbox/main/data/workspace.cljs b/frontend/src/uxbox/main/data/workspace.cljs index c0fa4f1f2..da4a8be35 100644 --- a/frontend/src/uxbox/main/data/workspace.cljs +++ b/frontend/src/uxbox/main/data/workspace.cljs @@ -7,7 +7,6 @@ (ns uxbox.main.data.workspace (:require [beicon.core :as rx] - ;; [uxbox.main.data.workspace.ruler :as wruler] [cljs.spec.alpha :as s] [potok.core :as ptk] [uxbox.config :as cfg] @@ -342,7 +341,6 @@ (defn add-shape [data] - {:pre [(us/valid? ::ds/shape data)]} (reify udp/IPageUpdate ptk/UpdateEvent @@ -426,32 +424,27 @@ ;; --- Update Shape Attrs -(deftype UpdateShapeAttrs [id attrs] - ptk/UpdateEvent - (update [_ state] - (update-in state [:shapes id] merge attrs))) - (defn update-shape-attrs [id attrs] - {:pre [(uuid? id) (us/valid? ::ds/attributes attrs)]} - (let [atts (us/extract attrs ::ds/attributes)] - (UpdateShapeAttrs. id attrs))) + (s/assert ::us/uuid id) + (s/assert ::ds/attributes attrs) + (let [atts (s/conform ::ds/attributes attrs)] + (reify + ptk/UpdateEvent + (update [_ state] + (update-in state [:shapes id] merge attrs))))) ;; --- Update Selected Shapes attrs - -(deftype UpdateSelectedShapesAttrs [attrs] - ptk/WatchEvent - (watch [_ state stream] - (let [pid (get-in state [:workspace :current]) - selected (get-in state [:workspace pid :selected])] - (rx/from-coll (map #(update-shape-attrs % attrs) selected))))) - (defn update-selected-shapes-attrs [attrs] - {:pre [(us/valid? ::ds/attributes attrs)]} - (UpdateSelectedShapesAttrs. attrs)) - + (s/assert ::ds/attributes attrs) + (reify + ptk/WatchEvent + (watch [_ state stream] + (let [pid (get-in state [:workspace :current]) + selected (get-in state [:workspace pid :selected])] + (rx/from-coll (map #(update-shape-attrs % attrs) selected)))))) ;; --- Move Selected @@ -485,48 +478,44 @@ (declare materialize-current-modifier) (declare apply-temporal-displacement) -(defrecord MoveSelected [direction speed] - ptk/WatchEvent - (watch [_ state stream] - (let [page-id (get-in state [:workspace :current]) - workspace (get-in state [:workspace page-id]) - selected (:selected workspace) - flags (:flags workspace) - align? (refs/alignment-activated? flags) - metadata (merge c/page-metadata (get-in state [:pages page-id :metadata])) - distance (get-displacement-distance metadata align?) - displacement (get-displacement direction speed distance)] - (rx/concat - (when align? - (rx/concat - (rx/from-coll (map initial-shape-align selected)) - (rx/from-coll (map apply-displacement selected)))) - (rx/from-coll (map #(apply-temporal-displacement % displacement) selected)) - (rx/from-coll (map materialize-current-modifier selected)))))) - (s/def ::direction #{:up :down :right :left}) (s/def ::speed #{:std :fast}) (defn move-selected [direction speed] - {:pre [(us/valid? ::direction direction) - (us/valid? ::speed speed)]} - (MoveSelected. direction speed)) + (s/assert ::direction direction) + (s/assert ::speed speed) + (reify + ptk/WatchEvent + (watch [_ state stream] + (let [page-id (get-in state [:workspace :current]) + workspace (get-in state [:workspace page-id]) + selected (:selected workspace) + flags (:flags workspace) + align? (refs/alignment-activated? flags) + metadata (merge c/page-metadata (get-in state [:pages page-id :metadata])) + distance (get-displacement-distance metadata align?) + displacement (get-displacement direction speed distance)] + (rx/concat + (when align? + (rx/concat + (rx/from-coll (map initial-shape-align selected)) + (rx/from-coll (map apply-displacement selected)))) + (rx/from-coll (map #(apply-temporal-displacement % displacement) selected)) + (rx/from-coll (map materialize-current-modifier selected))))))) ;; --- Move Selected Layer -(defrecord MoveSelectedLayer [loc] - udp/IPageUpdate - ptk/UpdateEvent - (update [_ state] - (let [id (get-in state [:workspace :current]) - selected (get-in state [:workspace id :selected])] - (ds/move-layer state selected loc)))) - (defn move-selected-layer [loc] - {:pre [(us/valid? ::direction loc)]} - (MoveSelectedLayer. loc)) + (assert (s/valid? ::direction loc)) + (reify + udp/IPageUpdate + ptk/UpdateEvent + (update [_ state] + (let [id (get-in state [:workspace :current]) + selected (get-in state [:workspace id :selected])] + (ds/move-layer state selected loc))))) ;; --- Update Shape Position @@ -708,22 +697,24 @@ ;; --- Update Dimensions -(deftype UpdateDimensions [id dimensions] - udp/IPageUpdate - ptk/UpdateEvent - (update [_ state] - (update-in state [:shapes id] geom/resize-dim dimensions))) +(s/def ::width (s/and ::us/number ::us/positive)) +(s/def ::height (s/and ::us/number ::us/positive)) -(s/def ::update-dimensions-opts +(s/def ::update-dimensions (s/keys :opt-un [::width ::height])) (defn update-dimensions "A helper event just for update the position of the shape using the width and height attrs instread final point of coordinates." - [id opts] - {:pre [(uuid? id) (us/valid? ::update-dimensions-opts opts)]} - (UpdateDimensions. id opts)) + [id dimensions] + (s/assert ::us/uuid id) + (s/assert ::update-dimensions dimensions) + (reify + udp/IPageUpdate + ptk/UpdateEvent + (update [_ state] + (update-in state [:shapes id] geom/resize-dim dimensions)))) ;; --- Update Interaction @@ -983,16 +974,15 @@ ;; Is a workspace aware wrapper over uxbox.data.pages/UpdateMetadata event. -(defrecord UpdateMetadata [id metadata] - ptk/WatchEvent - (watch [_ state s] - (rx/of (udp/update-metadata id metadata) - (initialize-alignment id)))) - (defn update-metadata [id metadata] - {:pre [(uuid? id) (us/valid? ::udp/metadata metadata)]} - (UpdateMetadata. id metadata)) + (s/assert ::us/uuid id) + (s/assert ::udp/metadata metadata) + (reify + ptk/WatchEvent + (watch [_ state s] + (rx/of (udp/update-metadata id metadata) + (initialize-alignment id))))) (defrecord OpenView [page-id] ptk/WatchEvent diff --git a/frontend/src/uxbox/main/ui/auth/login.cljs b/frontend/src/uxbox/main/ui/auth/login.cljs index 94d995fc1..7edd5d571 100644 --- a/frontend/src/uxbox/main/ui/auth/login.cljs +++ b/frontend/src/uxbox/main/ui/auth/login.cljs @@ -8,20 +8,22 @@ (ns uxbox.main.ui.auth.login (:require [rumext.alpha :as mf] - [struct.alpha :as s] + [cljs.spec.alpha :as s] [uxbox.builtins.icons :as i] [uxbox.config :as cfg] [uxbox.main.data.auth :as da] [uxbox.main.store :as st] [uxbox.main.ui.messages :refer [messages-widget]] [uxbox.util.dom :as dom] - [uxbox.util.forms :as fm] + [uxbox.util.forms2 :as fm2] [uxbox.util.i18n :refer [tr]] [uxbox.util.router :as rt])) -(s/defs ::login-form - (s/dict :username (s/&& ::s/string ::fm/not-empty-string) - :password (s/&& ::s/string ::fm/not-empty-string))) +(s/def ::username ::fm2/not-empty-string) +(s/def ::password ::fm2/not-empty-string) + +(s/def ::login-form + (s/keys :req-un [::username ::password])) (defn- on-submit [event form] @@ -42,7 +44,8 @@ (mf/defc login-form [] - (let [{:keys [data] :as form} (fm/use-form ::login-form {})] + (let [{:keys [data] :as form} (fm2/use-form ::login-form {})] + (prn "login-form" form) [:form {:on-submit #(on-submit % form)} [:div.login-content (when cfg/isdemo @@ -52,16 +55,18 @@ {:name "username" :tab-index "2" :value (:username data "") - :on-blur (fm/on-input-blur form :username) - :on-change (fm/on-input-change form :username) + :class (fm2/error-class form :username) + :on-blur (fm2/on-input-blur form :username) + :on-change (fm2/on-input-change form :username) :placeholder (tr "auth.email-or-username") :type "text"}] [:input.input-text {:name "password" :tab-index "3" :value (:password data "") - :on-blur (fm/on-input-blur form :password) - :on-change (fm/on-input-change form :password) + :class (fm2/error-class form :password) + :on-blur (fm2/on-input-blur form :password) + :on-change (fm2/on-input-change form :password) :placeholder (tr "auth.password") :type "password"}] [:input.btn-primary diff --git a/frontend/src/uxbox/main/ui/auth/register.cljs b/frontend/src/uxbox/main/ui/auth/register.cljs index 261a2a98a..e9fe97786 100644 --- a/frontend/src/uxbox/main/ui/auth/register.cljs +++ b/frontend/src/uxbox/main/ui/auth/register.cljs @@ -53,7 +53,6 @@ (mf/defc register-form [props] (let [{:keys [data] :as form} (fm/use-form ::register-form {})] - (prn "register-form" form) [:form {:on-submit #(on-submit % form)} [:div.login-content [:input.input-text diff --git a/frontend/src/uxbox/main/ui/settings/password.cljs b/frontend/src/uxbox/main/ui/settings/password.cljs index 9737f6725..fa559aabb 100644 --- a/frontend/src/uxbox/main/ui/settings/password.cljs +++ b/frontend/src/uxbox/main/ui/settings/password.cljs @@ -8,38 +8,40 @@ (ns uxbox.main.ui.settings.password (:require [rumext.alpha :as mf] - [struct.alpha :as s] + [cljs.spec.alpha :as s] [uxbox.builtins.icons :as i] [uxbox.main.data.users :as udu] [uxbox.main.store :as st] [uxbox.util.dom :as dom] - [uxbox.util.forms :as fm] + [uxbox.util.forms2 :as fm] [uxbox.util.i18n :refer [tr]] [uxbox.util.messages :as um])) +(defn- on-error + [form error] + (case (:code error) + :uxbox.services.users/old-password-not-match + (swap! form assoc-in [:errors :password-old] + {:type ::api :message "settings.password.wrong-old-password"}) + + :else (throw (ex-info "unexpected" {:error error})))) + (defn- on-submit [event form] - (letfn [(on-error [error] - (case (:code error) - :uxbox.services.users/old-password-not-match - (swap! form assoc-in [:errors :password-old] - {:type ::api :message "settings.password.wrong-old-password"}) + (dom/prevent-default event) + (let [data (:clean-data form) + opts {:on-success #(st/emit! (um/info (tr "settings.password.password-saved"))) + :on-error #(on-error form %)}] + (st/emit! (udu/update-password data opts)))) - :else (throw (ex-info "unexpected" {:error error})))) +(s/def ::password-1 ::fm/not-empty-string) +(s/def ::password-2 ::fm/not-empty-string) +(s/def ::password-old ::fm/not-empty-string) - (on-success [_] - (st/emit! (um/info (tr "settings.password.password-saved"))))] - - (dom/prevent-default event) - (let [data (:clean-data form) - opts {:on-success on-success - :on-error on-error}] - (st/emit! (udu/update-password data opts))))) - -(s/defs ::password-form - (s/dict :password-1 (s/&& ::s/string ::fm/not-empty-string) - :password-2 (s/&& ::s/string ::fm/not-empty-string) - :password-old (s/&& ::s/string ::fm/not-empty-string))) +(s/def ::password-form + (s/keys :req-un [::password-1 + ::password-2 + ::password-old])) (mf/defc password-form [props] @@ -54,7 +56,8 @@ :on-blur (fm/on-input-blur form :password-old) :on-change (fm/on-input-change form :password-old) :placeholder (tr "settings.password.old-password")}] - [:& fm/field-error {:form form :field :password-old}] + + [:& fm/field-error {:form form :field :password-old :type ::api}] [:input.input-text {:type "password" @@ -64,7 +67,7 @@ :on-blur (fm/on-input-blur form :password-1) :on-change (fm/on-input-change form :password-1) :placeholder (tr "settings.password.new-password")}] - [:& fm/field-error {:form form :field :password-1}] + ;; [:& fm/field-error {:form form :field :password-1}] [:input.input-text {:type "password" @@ -74,7 +77,7 @@ :on-blur (fm/on-input-blur form :password-2) :on-change (fm/on-input-change form :password-2) :placeholder (tr "settings.password.confirm-password")}] - [:& fm/field-error {:form form :field :password-2}] + ;; [:& fm/field-error {:form form :field :password-2}] [:input.btn-primary {:type "submit" diff --git a/frontend/src/uxbox/main/ui/settings/profile.cljs b/frontend/src/uxbox/main/ui/settings/profile.cljs index 814d36e52..584935ff3 100644 --- a/frontend/src/uxbox/main/ui/settings/profile.cljs +++ b/frontend/src/uxbox/main/ui/settings/profile.cljs @@ -7,16 +7,16 @@ (ns uxbox.main.ui.settings.profile (:require + [cljs.spec.alpha :as s] [cuerdas.core :as str] [lentes.core :as l] [rumext.alpha :as mf] - [struct.alpha :as s] [uxbox.builtins.icons :as i] [uxbox.main.data.users :as udu] [uxbox.main.store :as st] [uxbox.util.data :refer [read-string]] [uxbox.util.dom :as dom] - [uxbox.util.forms :as fm] + [uxbox.util.forms2 :as fm] [uxbox.util.i18n :as i18n :refer [tr]] [uxbox.util.interop :refer [iterable->seq]] [uxbox.util.messages :as um])) @@ -32,11 +32,16 @@ (-> (l/key :profile) (l/derive st/state))) -(s/defs ::profile-form - (s/dict :fullname (s/&& ::s/string ::fm/not-empty-string) - :username (s/&& ::s/string ::fm/not-empty-string) - :language (s/&& ::s/string ::fm/not-empty-string) - :email ::s/email)) +(s/def ::fullname ::fm/not-empty-string) +(s/def ::username ::fm/not-empty-string) +(s/def ::language ::fm/not-empty-string) +(s/def ::email ::fm/email) + +(s/def ::profile-form + (s/keys :req-un [::fullname + ::username + ::language + ::email])) (defn- on-error [error form] @@ -58,7 +63,6 @@ (defn- on-submit [event form] - (prn "on-submit" form) (dom/prevent-default event) (let [data (:clean-data form) on-success #(st/emit! (um/info (tr "settings.profile.profile-saved"))) diff --git a/frontend/src/uxbox/main/ui/shapes/common.cljs b/frontend/src/uxbox/main/ui/shapes/common.cljs index ef76169ec..c30f5b095 100644 --- a/frontend/src/uxbox/main/ui/shapes/common.cljs +++ b/frontend/src/uxbox/main/ui/shapes/common.cljs @@ -41,14 +41,12 @@ (watch [_ state stream] (let [pid (get-in state [:workspace :current]) selected (get-in state [:workspace pid :selected])] - (prn "start-move-selected" selected) (rx/from-coll (map start-move selected)))))) (defn on-mouse-down [event {:keys [id type] :as shape} selected] (let [selected? (contains? selected id) drawing? @refs/selected-drawing-tool] - (prn "on-mouse-down" id type selected? (= type :canvas)) (when-not (:blocked shape) (cond drawing? diff --git a/frontend/src/uxbox/main/ui/shapes/path.cljs b/frontend/src/uxbox/main/ui/shapes/path.cljs index 1cbd9b399..2889a1742 100644 --- a/frontend/src/uxbox/main/ui/shapes/path.cljs +++ b/frontend/src/uxbox/main/ui/shapes/path.cljs @@ -29,7 +29,6 @@ (common/on-mouse-down event shape selected)) (on-double-click [event] (when selected? - (prn "path-component$on-double-click") (st/emit! (dw/start-edition-mode (:id shape)))))] [:g.shape {:class (when selected? "selected") :on-double-click on-double-click diff --git a/frontend/src/uxbox/util/forms2.cljs b/frontend/src/uxbox/util/forms2.cljs new file mode 100644 index 000000000..8c3b8735c --- /dev/null +++ b/frontend/src/uxbox/util/forms2.cljs @@ -0,0 +1,145 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) 2015-2017 Andrey Antukh + +(ns uxbox.util.forms2 + (:refer-clojure :exclude [uuid]) + (:require + [beicon.core :as rx] + [cljs.spec.alpha :as s] + [cuerdas.core :as str] + [lentes.core :as l] + [potok.core :as ptk] + [rumext.alpha :as mf] + [uxbox.util.dom :as dom] + [uxbox.util.i18n :refer [tr]])) + +;; --- Handlers Helpers + +(defn- impl-mutator + [v update-fn] + (specify v + IReset + (-reset! [_ new-value] + (update-fn new-value)) + + ISwap + (-swap! + ([self f] (update-fn f)) + ([self f x] (update-fn #(f % x))) + ([self f x y] (update-fn #(f % x y))) + ([self f x y more] (update-fn #(apply f % x y more)))))) + +(defn- translate-error-type + [name] + "errors.undefined-error") + +(defn- interpret-problem + [acc {:keys [path pred val via in] :as problem}] + ;; (prn "interpret-problem" problem) + (cond + (and (empty? path) + (list? pred) + (= (first (last pred)) 'cljs.core/contains?)) + (let [path (conj path (last (last pred)))] + (assoc-in acc path {:name ::missing :type :builtin})) + + (and (not (empty? path)) + (not (empty? via))) + (assoc-in acc path {:name (last via) :type :builtin}) + + :else acc)) + +(defn use-form + [spec initial] + (let [[state update-state] (mf/useState {:data (if (fn? initial) (initial) initial) + :errors {} + :touched {}}) + clean-data (s/conform spec (:data state)) + problems (when (= ::s/invalid clean-data) + (::s/problems (s/explain-data spec (:data state)))) + + + errors (merge (reduce interpret-problem {} problems) + (:errors state))] + (-> (assoc state + :errors errors + :clean-data (when (not= clean-data ::s/invalid) clean-data) + :valid (and (empty? errors) + (not= clean-data ::s/invalid))) + (impl-mutator update-state)))) + +(defn on-input-change + [{:keys [data] :as form} field] + (fn [event] + (let [target (dom/get-target event) + value (dom/get-value target)] + (swap! form (fn [state] + (-> state + (assoc-in [:data field] value) + (update :errors dissoc field))))))) + +(defn on-input-blur + [{:keys [touched] :as form} field] + (fn [event] + (let [target (dom/get-target event)] + (when-not (get touched field) + (swap! form assoc-in [:touched field] true))))) + +;; --- Helper Components + +(mf/defc field-error + [{:keys [form field type] + :or {only (constantly true)} + :as props}] + (let [touched? (get-in form [:touched field]) + {:keys [message code] :as error} (get-in form [:errors field])] + (when (and touched? error + (cond + (nil? type) true + (keyword? type) (= (:type error) type) + (ifn? type) (type (:type error)) + :else false)) + (prn "field-error" error) + [:ul.form-errors + [:li {:key code} (tr message)]]))) + +(defn error-class + [form field] + (when (and (get-in form [:errors field]) + (get-in form [:touched field])) + "invalid")) + +;; --- Form Validation Api + +;; --- Form Specs and Conformers + +(def ^:private email-re + #"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$") + +(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 ::not-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 %)))) diff --git a/frontend/src/uxbox/util/messages.cljs b/frontend/src/uxbox/util/messages.cljs index edd1103d4..f5d50a987 100644 --- a/frontend/src/uxbox/util/messages.cljs +++ b/frontend/src/uxbox/util/messages.cljs @@ -25,11 +25,34 @@ (def +animation-timeout+ 600) -;; --- Message Event +;; --- Main API (declare hide) +(declare show) (declare show?) +(defn error + [message & {:keys [timeout] :or {timeout 3000}}] + (show {:content message + :type :error + :timeout timeout})) + +(defn info + [message & {:keys [timeout] :or {timeout 3000}}] + (show {:content message + :type :info + :timeout timeout})) + +(defn dialog + [message & {:keys [on-accept on-cancel]}] + (show {:content message + :on-accept on-accept + :on-cancel on-cancel + :timeout js/Number.MAX_SAFE_INTEGER + :type :dialog})) + +;; --- Show Event + (defn show [data] (reify @@ -53,47 +76,19 @@ [v] (= ::show (ptk/type v))) -(defn error - [message & {:keys [timeout] :or {timeout 3000}}] - (show {:content message - :type :error - :timeout timeout})) - -(defn info - [message & {:keys [timeout] :or {timeout 3000}}] - (show {:content message - :type :info - :timeout timeout})) - -(defn dialog - [message & {:keys [on-accept on-cancel]}] - (show {:content message - :on-accept on-accept - :on-cancel on-cancel - :timeout js/Number.MAX_SAFE_INTEGER - :type :dialog})) - -;; --- Hide Message +;; --- Hide Event (defn hide [] - (let [canceled? (volatile! {})] - (reify - ptk/UpdateEvent - (update [_ state] - (update state :message - (fn [v] - (if (nil? v) - (do (vreset! canceled? true) nil) - (assoc v :state :hide))))) - - ptk/WatchEvent - (watch [_ state stream] - (if @canceled? - (rx/empty) - (->> (rx/of #(dissoc % :message)) - (rx/delay +animation-timeout+))))))) + (reify + ptk/UpdateEvent + (update [_ state] + (update state :message assoc :state :hide)) + ptk/WatchEvent + (watch [_ state stream] + (->> (rx/of #(dissoc % :message)) + (rx/delay +animation-timeout+))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; UI Components @@ -145,6 +140,7 @@ (mf/defc messages-widget [{:keys [message] :as props}] + (prn "messages-widget" props) (case (:type message) :error (mf/element notification-box props) :info (mf/element notification-box props) diff --git a/frontend/src/uxbox/util/spec.cljs b/frontend/src/uxbox/util/spec.cljs index 070824a79..592cbf11a 100644 --- a/frontend/src/uxbox/util/spec.cljs +++ b/frontend/src/uxbox/util/spec.cljs @@ -42,6 +42,12 @@ (s/def ::uuid uuid?) (s/def ::email email?) (s/def ::color color?) +(s/def ::string string?) +(s/def ::number number?) +(s/def ::positive pos?) +(s/def ::inst inst?) +(s/def ::keyword keyword?) +(s/def ::fn fn?) ;; --- Public Api