🚧 More work on data and forms validation.

This commit is contained in:
Andrey Antukh 2019-09-09 12:34:31 +02:00
parent 2477b289e2
commit a009961a58
15 changed files with 464 additions and 314 deletions

View file

@ -6,7 +6,7 @@
(ns uxbox.main.data.auth (ns uxbox.main.data.auth
(:require (:require
[struct.alpha :as st] [cljs.spec.alpha :as s]
[beicon.core :as rx] [beicon.core :as rx]
[potok.core :as ptk] [potok.core :as ptk]
[uxbox.main.repo :as rp] [uxbox.main.repo :as rp]
@ -14,9 +14,15 @@
[uxbox.main.data.users :as du] [uxbox.main.data.users :as du]
[uxbox.util.messages :as um] [uxbox.util.messages :as um]
[uxbox.util.router :as rt] [uxbox.util.router :as rt]
[uxbox.util.spec :as us]
[uxbox.util.i18n :as i18n :refer [tr]] [uxbox.util.i18n :as i18n :refer [tr]]
[uxbox.util.storage :refer [storage]])) [uxbox.util.storage :refer [storage]]))
(s/def ::username string?)
(s/def ::password string?)
(s/def ::fullname string?)
(s/def ::email ::us/email)
;; --- Logged In ;; --- Logged In
;; TODO: add spec ;; TODO: add spec
@ -43,13 +49,12 @@
;; --- Login ;; --- Login
(st/defs ::login (s/def ::login-params
(st/dict :username ::st/string (s/keys :req-un [::username ::password]))
:password ::st/string))
(defn login (defn login
[{:keys [username password] :as data}] [{:keys [username password] :as data}]
(assert (st/valid? ::login data)) (s/assert ::login-params data)
(reify (reify
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
@ -93,17 +98,17 @@
;; --- Register ;; --- Register
(st/defs ::register (s/def ::register-params
(st/dict :fullname ::st/string (s/keys :req-un [::fullname
:username ::st/string ::username
:password ::st/string ::password
:email ::st/email)) ::email]))
(defn register (defn register
"Create a register event instance." "Create a register event instance."
[data on-error] [data on-error]
(assert (st/valid? ::register data)) (s/assert ::register-params data)
(assert (fn? on-error)) (s/assert ::us/fn on-error)
(reify (reify
ptk/WatchEvent ptk/WatchEvent
(watch [_ state stream] (watch [_ state stream]
@ -122,12 +127,12 @@
;; --- Recovery Request ;; --- Recovery Request
(st/defs ::recovery-request (s/def ::recovery-request-params
(st/dict :username ::st/string)) (s/keys :req-un [::username]))
(defn recovery-request (defn recovery-request
[data] [data]
(assert (st/valid? ::recovery-request data)) (s/assert ::recovery-request-params data)
(reify (reify
ptk/WatchEvent ptk/WatchEvent
(watch [_ state stream] (watch [_ state stream]
@ -164,13 +169,13 @@
;; --- Recovery (Password) ;; --- Recovery (Password)
(st/defs ::recovery (s/def ::token string?)
(st/dict :username ::st/string (s/def ::recovery-params
:token ::st/string)) (s/keys :req-un [::username ::token]))
(defn recovery (defn recovery
[{:keys [token password] :as data}] [{:keys [token password] :as data}]
(assert (st/valid? ::recovery data)) (s/assert ::recovery-params data)
(reify (reify
ptk/WatchEvent ptk/WatchEvent
(watch [_ state stream] (watch [_ state stream]

View file

@ -6,10 +6,10 @@
(ns uxbox.main.data.pages (ns uxbox.main.data.pages
(:require (:require
[cljs.spec.alpha :as s]
[beicon.core :as rx] [beicon.core :as rx]
[cuerdas.core :as str] [cuerdas.core :as str]
[potok.core :as ptk] [potok.core :as ptk]
[struct.alpha :as st]
[uxbox.main.repo :as rp] [uxbox.main.repo :as rp]
[uxbox.util.data :refer [index-by-id]] [uxbox.util.data :refer [index-by-id]]
[uxbox.util.spec :as us] [uxbox.util.spec :as us]
@ -18,54 +18,65 @@
;; --- Struct ;; --- Struct
(st/defs ::inst inst?) (s/def ::id ::us/uuid)
(st/defs ::width (st/&& ::st/number ::st/positive)) (s/def ::name ::us/string)
(st/defs ::height (st/&& ::st/number ::st/positive)) (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 (s/def ::metadata
(st/dict :width ::width (s/keys :req-un [::width ::height]
:height ::height :opt-un [::grid-y-axis
:grid-y-axis (st/opt ::st/number) ::grid-x-axis
:grid-x-axis (st/opt ::st/number) ::grid-color
:grid-color (st/opt ::st/string) ::order
:order (st/opt ::st/number) ::background
:background (st/opt ::st/string) ::background-opacity]))
:background-opacity (st/opt ::st/number)))
(st/defs ::shapes-list (s/def ::shapes
(st/coll-of ::st/uuid)) (s/coll-of ::us/uuid :kind vector? :into []))
(st/defs ::page-entity (s/def ::page-entity
(st/dict :id ::st/uuid (s/keys :req-un [::id
:name ::st/string ::name
:project ::st/uuid ::project
:created-at ::inst ::created-at
:modified-at ::inst ::modified-at
:user ::st/uuid ::user
:metadata ::metadata ::metadata
:shapes ::shapes-list)) ::shapes]))
(st/defs ::minimal-shape (s/def ::minimal-shape
(st/dict :id ::st/uuid (s/keys :req-un [::type ::name]
:type ::st/keyword :opt-un [::id]))
:name ::st/string))
(st/defs ::server-page-data-sapes (s/def :uxbox.backend/shapes
(st/coll-of ::minimal-shape)) (s/coll-of ::minimal-shape :kind vector?))
(st/defs ::server-page-data (s/def :uxbox.backend/data
(st/dict :shapes ::server-page-data-sapes)) (s/keys :req-un [:uxbox.backend/shapes]))
(st/defs ::server-page (s/def ::server-page
(st/dict :id ::st/uuid (s/keys :req-un [::id ::name
:name ::st/string ::project
:project ::st/uuid ::version
:version ::st/integer ::created-at
:created-at ::inst ::modified-at
:modified-at ::inst ::user
:user ::st/uuid ::metadata
:metadata ::metadata :uxbox.backend/data]))
:data ::server-page-data))
;; --- Protocols ;; --- Protocols
@ -173,15 +184,12 @@
(declare rehash-pages) (declare rehash-pages)
(st/defs ::page-created (s/def ::page-created-params
(st/dict :id ::st/uuid (s/keys :req-un [::id ::name ::project ::metadata]))
:name ::st/string
:project ::st/uuid
:metadata ::metadata))
(defn page-created (defn page-created
[data] [data]
(assert (st/valid? ::page-created data) "invalid parameters") (s/assert ::page-created-event data)
(reify (reify
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
@ -197,15 +205,12 @@
;; --- Create Page ;; --- Create Page
(st/defs ::create-page (s/def ::created-page-params
(st/dict :name ::st/string (s/keys :req-un [::name ::project ::width ::height]))
:project ::st/uuid
:width ::width
:height ::height))
(defn create-page (defn create-page
[{:keys [name project width height layout] :as data}] [{:keys [name project width height layout] :as data}]
(assert (st/valid? ::create-page data)) (s/assert ::created-page-params data)
(reify (reify
ptk/WatchEvent ptk/WatchEvent
(watch [this state s] (watch [this state s]
@ -231,7 +236,7 @@
(defn page-persisted (defn page-persisted
[data] [data]
(assert (st/valid? ::server-page data)) (s/assert ::server-page data)
(reify (reify
cljs.core/IDeref cljs.core/IDeref
(-deref [_] data) (-deref [_] data)
@ -278,24 +283,24 @@
;; --- Page Metadata Persisted ;; --- Page Metadata Persisted
(deftype MetadataPersisted [id data] (s/def ::metadata-persisted-params
ptk/UpdateEvent (s/keys :req-un [::id ::version]))
(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))
(defn metadata-persisted (defn metadata-persisted
[{:keys [id] :as data}] [{:keys [id] :as data}]
{:pre [(st/valid? ::metadata-persisted-event data)]} (s/assert ::metadata-persisted-params data)
(MetadataPersisted. id 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 ;; --- Persist Page Metadata
@ -316,29 +321,28 @@
;; --- Update Page ;; --- Update Page
(deftype UpdatePage [id data] (defn update-page
[id data]
(s/assert ::page-entity data)
(s/assert ::id id)
(reify
IPageUpdate IPageUpdate
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(update-in state [:pages id] merge (dissoc data :id :version)))) (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))
;; --- Update Page Metadata ;; --- Update Page Metadata
(deftype UpdateMetadata [id metadata] (defn update-metadata
[id metadata]
(s/assert ::id id)
(s/assert ::metadata metadata)
(reify
IMetadataUpdate IMetadataUpdate
ptk/UpdateEvent ptk/UpdateEvent
(update [this state] (update [this state]
(assoc-in state [:pages id :metadata] metadata))) (assoc-in state [:pages id :metadata] metadata))))
(defn update-metadata
[id metadata]
{:pre [(uuid? id) (st/valid? ::metadata metadata)]}
(UpdateMetadata. id metadata))
;; --- Rehash Pages ;; --- Rehash Pages
;; ;;
@ -382,24 +386,21 @@
;; A specialized event for persist data ;; A specialized event for persist data
;; from the update page form. ;; from the update page form.
(st/defs ::persist-page-update-form (s/def ::persist-page-update-form-params
(st/dict :id ::st/uuid (s/keys :req-un [::id ::name ::width ::height]))
:name ::st/string
:width ::width
:height ::height))
(defn persist-page-update-form (defn persist-page-update-form
[{:keys [id name width height] :as data}] [{:keys [id name width height] :as data}]
(assert (st/valid? ::persist-page-update-form data)) (s/assert ::persist-page-update-form-params data)
(reify (reify
ptk/WatchEvent ptk/UpdateEvent
(watch [_ state stream] (update [_ state]
(let [page (-> (get-in state [:pages id]) (update-in state [:pages id]
(fn [page]
(-> (assoc page :name name)
(assoc-in [:name] name) (assoc-in [:name] name)
(assoc-in [:metadata :width] width) (assoc-in [:metadata :width] width)
(assoc-in [:metadata :height] height))] (assoc-in [:metadata :height] height)))))))
(rx/of (update-page id page))))))
;; --- Delete Page (by id) ;; --- Delete Page (by id)

View file

@ -10,7 +10,6 @@
[cuerdas.core :as str] [cuerdas.core :as str]
[beicon.core :as rx] [beicon.core :as rx]
[potok.core :as ptk] [potok.core :as ptk]
[struct.core :as st]
[uxbox.main.repo :as rp] [uxbox.main.repo :as rp]
[uxbox.main.data.pages :as udp] [uxbox.main.data.pages :as udp]
[uxbox.util.uuid :as uuid] [uxbox.util.uuid :as uuid]
@ -35,21 +34,12 @@
::created-at ::created-at
::modified-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 ;; --- Helpers
(defn assoc-project (defn assoc-project
"A reduce function for assoc the project to the state map." "A reduce function for assoc the project to the state map."
[state {:keys [id] :as project}] [state {:keys [id] :as project}]
(assert (st/valid? project-spec project) (s/assert ::project-entity project)
"invalid project instance")
(update-in state [:projects id] merge project)) (update-in state [:projects id] merge project))
(defn dissoc-project (defn dissoc-project
@ -170,15 +160,12 @@
;; --- Create Project ;; --- Create Project
(st/defs create-project-spec (s/def ::create-project-params
{:name [st/required st/string] (s/keys :req-un [::name ::width ::height]))
:width [st/required st/number st/positive]
:height [st/required st/number st/positive]})
(defn create-project (defn create-project
[{:keys [name] :as params}] [{:keys [name] :as params}]
(assert (st/valid? create-project-spec params) (s/assert ::create-project-params params)
"invalid params for create project event")
(reify (reify
ptk/WatchEvent ptk/WatchEvent
(watch [this state stream] (watch [this state stream]

View file

@ -5,14 +5,17 @@
;; Copyright (c) 2015-2019 Andrey Antukh <niwi@niwi.nz> ;; Copyright (c) 2015-2019 Andrey Antukh <niwi@niwi.nz>
(ns uxbox.main.data.shapes (ns uxbox.main.data.shapes
(:require [cljs.spec.alpha :as s] (:require
[cljs.spec.alpha :as s]
[uxbox.main.geom :as geom] [uxbox.main.geom :as geom]
[uxbox.util.data :refer [index-of]]
[uxbox.util.geom.matrix :as gmt] [uxbox.util.geom.matrix :as gmt]
[uxbox.util.uuid :as uuid] [uxbox.util.spec :as us]
[uxbox.util.data :refer [index-of]])) [uxbox.util.uuid :as uuid]))
;; --- Specs ;; --- Specs
(s/def ::id ::us/uuid)
(s/def ::blocked boolean?) (s/def ::blocked boolean?)
(s/def ::collapsed boolean?) (s/def ::collapsed boolean?)
(s/def ::content string?) (s/def ::content string?)
@ -49,7 +52,7 @@
(s/def ::attributes (s/def ::attributes
(s/keys :opt-un [::blocked (s/keys :opt-un [::blocked
::collapsed ::collapsed
::conent ::content
::fill-color ::fill-color
::fill-opacity ::fill-opacity
::font-family ::font-family
@ -72,10 +75,10 @@
::y1 ::y2])) ::y1 ::y2]))
(s/def ::minimal-shape (s/def ::minimal-shape
(s/keys ::req-un [::id ::page ::type ::name])) (s/keys :req-un [::id ::page ::type ::name]))
(s/def ::shape (s/def ::shape
(s/merge ::minimal-shape ::attributes)) (s/and ::minimal-shape ::attributes))
(s/def ::rect-like-shape (s/def ::rect-like-shape
(s/keys :req-un [::x1 ::y1 ::x2 ::y2 ::type])) (s/keys :req-un [::x1 ::y1 ::x2 ::y2 ::type]))

View file

@ -6,8 +6,8 @@
(ns uxbox.main.data.users (ns uxbox.main.data.users
(:require (:require
[cljs.spec.alpha :as s]
[beicon.core :as rx] [beicon.core :as rx]
[struct.core :as s]
[potok.core :as ptk] [potok.core :as ptk]
[uxbox.main.repo :as rp] [uxbox.main.repo :as rp]
[uxbox.util.i18n :as i18n :refer [tr]] [uxbox.util.i18n :as i18n :refer [tr]]
@ -58,17 +58,22 @@
;; --- Update Profile ;; --- Update Profile
(s/defs update-profile-spec (s/def ::fullname string?)
{:fullname [s/required s/string] (s/def ::email ::us/email)
:email [s/required s/email] (s/def ::password string?)
:username [s/required s/string] (s/def ::language string?)
:language [s/required s/string]})
(s/def ::update-profile-params
(s/keys :req-un [::fullname
::email
::username
::language]))
(defn update-profile (defn update-profile
[data {:keys [on-success on-error]}] [data {:keys [on-success on-error]}]
{:pre [(s/valid? update-profile-spec data) (s/assert ::update-profile-params data)
(fn? on-error) (s/assert ::us/fn on-error)
(fn? on-success)]} (s/assert ::us/fn on-success)
(reify (reify
ptk/WatchEvent ptk/WatchEvent
(watch [_ state s] (watch [_ state s]
@ -89,16 +94,20 @@
;; --- Update Password (Form) ;; --- Update Password (Form)
(s/defs update-password-spec (s/def ::password-1 string?)
{:password-1 [s/required s/string] (s/def ::password-2 string?)
:password-2 [s/required s/string [s/identical-to :password-1]] (s/def ::password-old string?)
:password-old [s/required s/string]})
(s/def ::update-password-params
(s/keys :req-un [::password-1
::password-2
::password-old]))
(defn update-password (defn update-password
[data {:keys [on-success on-error]}] [data {:keys [on-success on-error]}]
{:pre [(s/valid? update-password-spec data) (s/assert ::update-password-params data)
(fn? on-success) (s/assert ::us/fn on-success)
(fn? on-error)]} (s/assert ::us/fn on-error)
(reify (reify
ptk/WatchEvent ptk/WatchEvent
(watch [_ state s] (watch [_ state s]

View file

@ -7,7 +7,6 @@
(ns uxbox.main.data.workspace (ns uxbox.main.data.workspace
(:require (:require
[beicon.core :as rx] [beicon.core :as rx]
;; [uxbox.main.data.workspace.ruler :as wruler]
[cljs.spec.alpha :as s] [cljs.spec.alpha :as s]
[potok.core :as ptk] [potok.core :as ptk]
[uxbox.config :as cfg] [uxbox.config :as cfg]
@ -342,7 +341,6 @@
(defn add-shape (defn add-shape
[data] [data]
{:pre [(us/valid? ::ds/shape data)]}
(reify (reify
udp/IPageUpdate udp/IPageUpdate
ptk/UpdateEvent ptk/UpdateEvent
@ -426,32 +424,27 @@
;; --- Update Shape Attrs ;; --- Update Shape Attrs
(deftype UpdateShapeAttrs [id attrs]
ptk/UpdateEvent
(update [_ state]
(update-in state [:shapes id] merge attrs)))
(defn update-shape-attrs (defn update-shape-attrs
[id attrs] [id attrs]
{:pre [(uuid? id) (us/valid? ::ds/attributes attrs)]} (s/assert ::us/uuid id)
(let [atts (us/extract attrs ::ds/attributes)] (s/assert ::ds/attributes attrs)
(UpdateShapeAttrs. id attrs))) (let [atts (s/conform ::ds/attributes attrs)]
(reify
ptk/UpdateEvent
(update [_ state]
(update-in state [:shapes id] merge attrs)))))
;; --- Update Selected Shapes attrs ;; --- Update Selected Shapes attrs
(defn update-selected-shapes-attrs
(deftype UpdateSelectedShapesAttrs [attrs] [attrs]
(s/assert ::ds/attributes attrs)
(reify
ptk/WatchEvent ptk/WatchEvent
(watch [_ state stream] (watch [_ state stream]
(let [pid (get-in state [:workspace :current]) (let [pid (get-in state [:workspace :current])
selected (get-in state [:workspace pid :selected])] selected (get-in state [:workspace pid :selected])]
(rx/from-coll (map #(update-shape-attrs % attrs) selected))))) (rx/from-coll (map #(update-shape-attrs % attrs) selected))))))
(defn update-selected-shapes-attrs
[attrs]
{:pre [(us/valid? ::ds/attributes attrs)]}
(UpdateSelectedShapesAttrs. attrs))
;; --- Move Selected ;; --- Move Selected
@ -485,7 +478,14 @@
(declare materialize-current-modifier) (declare materialize-current-modifier)
(declare apply-temporal-displacement) (declare apply-temporal-displacement)
(defrecord MoveSelected [direction speed] (s/def ::direction #{:up :down :right :left})
(s/def ::speed #{:std :fast})
(defn move-selected
[direction speed]
(s/assert ::direction direction)
(s/assert ::speed speed)
(reify
ptk/WatchEvent ptk/WatchEvent
(watch [_ state stream] (watch [_ state stream]
(let [page-id (get-in state [:workspace :current]) (let [page-id (get-in state [:workspace :current])
@ -502,31 +502,20 @@
(rx/from-coll (map initial-shape-align selected)) (rx/from-coll (map initial-shape-align selected))
(rx/from-coll (map apply-displacement selected)))) (rx/from-coll (map apply-displacement selected))))
(rx/from-coll (map #(apply-temporal-displacement % displacement) selected)) (rx/from-coll (map #(apply-temporal-displacement % displacement) selected))
(rx/from-coll (map materialize-current-modifier 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))
;; --- Move Selected Layer ;; --- Move Selected Layer
(defrecord MoveSelectedLayer [loc] (defn move-selected-layer
[loc]
(assert (s/valid? ::direction loc))
(reify
udp/IPageUpdate udp/IPageUpdate
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(let [id (get-in state [:workspace :current]) (let [id (get-in state [:workspace :current])
selected (get-in state [:workspace id :selected])] selected (get-in state [:workspace id :selected])]
(ds/move-layer state selected loc)))) (ds/move-layer state selected loc)))))
(defn move-selected-layer
[loc]
{:pre [(us/valid? ::direction loc)]}
(MoveSelectedLayer. loc))
;; --- Update Shape Position ;; --- Update Shape Position
@ -708,22 +697,24 @@
;; --- Update Dimensions ;; --- Update Dimensions
(deftype UpdateDimensions [id dimensions] (s/def ::width (s/and ::us/number ::us/positive))
udp/IPageUpdate (s/def ::height (s/and ::us/number ::us/positive))
ptk/UpdateEvent
(update [_ state]
(update-in state [:shapes id] geom/resize-dim dimensions)))
(s/def ::update-dimensions-opts (s/def ::update-dimensions
(s/keys :opt-un [::width ::height])) (s/keys :opt-un [::width ::height]))
(defn update-dimensions (defn update-dimensions
"A helper event just for update the position "A helper event just for update the position
of the shape using the width and height attrs of the shape using the width and height attrs
instread final point of coordinates." instread final point of coordinates."
[id opts] [id dimensions]
{:pre [(uuid? id) (us/valid? ::update-dimensions-opts opts)]} (s/assert ::us/uuid id)
(UpdateDimensions. id opts)) (s/assert ::update-dimensions dimensions)
(reify
udp/IPageUpdate
ptk/UpdateEvent
(update [_ state]
(update-in state [:shapes id] geom/resize-dim dimensions))))
;; --- Update Interaction ;; --- Update Interaction
@ -983,16 +974,15 @@
;; Is a workspace aware wrapper over uxbox.data.pages/UpdateMetadata event. ;; Is a workspace aware wrapper over uxbox.data.pages/UpdateMetadata event.
(defrecord UpdateMetadata [id metadata] (defn update-metadata
[id metadata]
(s/assert ::us/uuid id)
(s/assert ::udp/metadata metadata)
(reify
ptk/WatchEvent ptk/WatchEvent
(watch [_ state s] (watch [_ state s]
(rx/of (udp/update-metadata id metadata) (rx/of (udp/update-metadata id metadata)
(initialize-alignment id)))) (initialize-alignment id)))))
(defn update-metadata
[id metadata]
{:pre [(uuid? id) (us/valid? ::udp/metadata metadata)]}
(UpdateMetadata. id metadata))
(defrecord OpenView [page-id] (defrecord OpenView [page-id]
ptk/WatchEvent ptk/WatchEvent

View file

@ -8,20 +8,22 @@
(ns uxbox.main.ui.auth.login (ns uxbox.main.ui.auth.login
(:require (:require
[rumext.alpha :as mf] [rumext.alpha :as mf]
[struct.alpha :as s] [cljs.spec.alpha :as s]
[uxbox.builtins.icons :as i] [uxbox.builtins.icons :as i]
[uxbox.config :as cfg] [uxbox.config :as cfg]
[uxbox.main.data.auth :as da] [uxbox.main.data.auth :as da]
[uxbox.main.store :as st] [uxbox.main.store :as st]
[uxbox.main.ui.messages :refer [messages-widget]] [uxbox.main.ui.messages :refer [messages-widget]]
[uxbox.util.dom :as dom] [uxbox.util.dom :as dom]
[uxbox.util.forms :as fm] [uxbox.util.forms2 :as fm2]
[uxbox.util.i18n :refer [tr]] [uxbox.util.i18n :refer [tr]]
[uxbox.util.router :as rt])) [uxbox.util.router :as rt]))
(s/defs ::login-form (s/def ::username ::fm2/not-empty-string)
(s/dict :username (s/&& ::s/string ::fm/not-empty-string) (s/def ::password ::fm2/not-empty-string)
:password (s/&& ::s/string ::fm/not-empty-string)))
(s/def ::login-form
(s/keys :req-un [::username ::password]))
(defn- on-submit (defn- on-submit
[event form] [event form]
@ -42,7 +44,8 @@
(mf/defc login-form (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)} [:form {:on-submit #(on-submit % form)}
[:div.login-content [:div.login-content
(when cfg/isdemo (when cfg/isdemo
@ -52,16 +55,18 @@
{:name "username" {:name "username"
:tab-index "2" :tab-index "2"
:value (:username data "") :value (:username data "")
:on-blur (fm/on-input-blur form :username) :class (fm2/error-class form :username)
:on-change (fm/on-input-change form :username) :on-blur (fm2/on-input-blur form :username)
:on-change (fm2/on-input-change form :username)
:placeholder (tr "auth.email-or-username") :placeholder (tr "auth.email-or-username")
:type "text"}] :type "text"}]
[:input.input-text [:input.input-text
{:name "password" {:name "password"
:tab-index "3" :tab-index "3"
:value (:password data "") :value (:password data "")
:on-blur (fm/on-input-blur form :password) :class (fm2/error-class form :password)
:on-change (fm/on-input-change form :password) :on-blur (fm2/on-input-blur form :password)
:on-change (fm2/on-input-change form :password)
:placeholder (tr "auth.password") :placeholder (tr "auth.password")
:type "password"}] :type "password"}]
[:input.btn-primary [:input.btn-primary

View file

@ -53,7 +53,6 @@
(mf/defc register-form (mf/defc register-form
[props] [props]
(let [{:keys [data] :as form} (fm/use-form ::register-form {})] (let [{:keys [data] :as form} (fm/use-form ::register-form {})]
(prn "register-form" form)
[:form {:on-submit #(on-submit % form)} [:form {:on-submit #(on-submit % form)}
[:div.login-content [:div.login-content
[:input.input-text [:input.input-text

View file

@ -8,18 +8,17 @@
(ns uxbox.main.ui.settings.password (ns uxbox.main.ui.settings.password
(:require (:require
[rumext.alpha :as mf] [rumext.alpha :as mf]
[struct.alpha :as s] [cljs.spec.alpha :as s]
[uxbox.builtins.icons :as i] [uxbox.builtins.icons :as i]
[uxbox.main.data.users :as udu] [uxbox.main.data.users :as udu]
[uxbox.main.store :as st] [uxbox.main.store :as st]
[uxbox.util.dom :as dom] [uxbox.util.dom :as dom]
[uxbox.util.forms :as fm] [uxbox.util.forms2 :as fm]
[uxbox.util.i18n :refer [tr]] [uxbox.util.i18n :refer [tr]]
[uxbox.util.messages :as um])) [uxbox.util.messages :as um]))
(defn- on-submit (defn- on-error
[event form] [form error]
(letfn [(on-error [error]
(case (:code error) (case (:code error)
:uxbox.services.users/old-password-not-match :uxbox.services.users/old-password-not-match
(swap! form assoc-in [:errors :password-old] (swap! form assoc-in [:errors :password-old]
@ -27,19 +26,22 @@
:else (throw (ex-info "unexpected" {:error error})))) :else (throw (ex-info "unexpected" {:error error}))))
(on-success [_] (defn- on-submit
(st/emit! (um/info (tr "settings.password.password-saved"))))] [event form]
(dom/prevent-default event) (dom/prevent-default event)
(let [data (:clean-data form) (let [data (:clean-data form)
opts {:on-success on-success opts {:on-success #(st/emit! (um/info (tr "settings.password.password-saved")))
:on-error on-error}] :on-error #(on-error form %)}]
(st/emit! (udu/update-password data opts))))) (st/emit! (udu/update-password data opts))))
(s/defs ::password-form (s/def ::password-1 ::fm/not-empty-string)
(s/dict :password-1 (s/&& ::s/string ::fm/not-empty-string) (s/def ::password-2 ::fm/not-empty-string)
:password-2 (s/&& ::s/string ::fm/not-empty-string) (s/def ::password-old ::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 (mf/defc password-form
[props] [props]
@ -54,7 +56,8 @@
:on-blur (fm/on-input-blur form :password-old) :on-blur (fm/on-input-blur form :password-old)
:on-change (fm/on-input-change form :password-old) :on-change (fm/on-input-change form :password-old)
:placeholder (tr "settings.password.old-password")}] :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 [:input.input-text
{:type "password" {:type "password"
@ -64,7 +67,7 @@
:on-blur (fm/on-input-blur form :password-1) :on-blur (fm/on-input-blur form :password-1)
:on-change (fm/on-input-change form :password-1) :on-change (fm/on-input-change form :password-1)
:placeholder (tr "settings.password.new-password")}] :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 [:input.input-text
{:type "password" {:type "password"
@ -74,7 +77,7 @@
:on-blur (fm/on-input-blur form :password-2) :on-blur (fm/on-input-blur form :password-2)
:on-change (fm/on-input-change form :password-2) :on-change (fm/on-input-change form :password-2)
:placeholder (tr "settings.password.confirm-password")}] :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 [:input.btn-primary
{:type "submit" {:type "submit"

View file

@ -7,16 +7,16 @@
(ns uxbox.main.ui.settings.profile (ns uxbox.main.ui.settings.profile
(:require (:require
[cljs.spec.alpha :as s]
[cuerdas.core :as str] [cuerdas.core :as str]
[lentes.core :as l] [lentes.core :as l]
[rumext.alpha :as mf] [rumext.alpha :as mf]
[struct.alpha :as s]
[uxbox.builtins.icons :as i] [uxbox.builtins.icons :as i]
[uxbox.main.data.users :as udu] [uxbox.main.data.users :as udu]
[uxbox.main.store :as st] [uxbox.main.store :as st]
[uxbox.util.data :refer [read-string]] [uxbox.util.data :refer [read-string]]
[uxbox.util.dom :as dom] [uxbox.util.dom :as dom]
[uxbox.util.forms :as fm] [uxbox.util.forms2 :as fm]
[uxbox.util.i18n :as i18n :refer [tr]] [uxbox.util.i18n :as i18n :refer [tr]]
[uxbox.util.interop :refer [iterable->seq]] [uxbox.util.interop :refer [iterable->seq]]
[uxbox.util.messages :as um])) [uxbox.util.messages :as um]))
@ -32,11 +32,16 @@
(-> (l/key :profile) (-> (l/key :profile)
(l/derive st/state))) (l/derive st/state)))
(s/defs ::profile-form (s/def ::fullname ::fm/not-empty-string)
(s/dict :fullname (s/&& ::s/string ::fm/not-empty-string) (s/def ::username ::fm/not-empty-string)
:username (s/&& ::s/string ::fm/not-empty-string) (s/def ::language ::fm/not-empty-string)
:language (s/&& ::s/string ::fm/not-empty-string) (s/def ::email ::fm/email)
:email ::s/email))
(s/def ::profile-form
(s/keys :req-un [::fullname
::username
::language
::email]))
(defn- on-error (defn- on-error
[error form] [error form]
@ -58,7 +63,6 @@
(defn- on-submit (defn- on-submit
[event form] [event form]
(prn "on-submit" form)
(dom/prevent-default event) (dom/prevent-default event)
(let [data (:clean-data form) (let [data (:clean-data form)
on-success #(st/emit! (um/info (tr "settings.profile.profile-saved"))) on-success #(st/emit! (um/info (tr "settings.profile.profile-saved")))

View file

@ -41,14 +41,12 @@
(watch [_ state stream] (watch [_ state stream]
(let [pid (get-in state [:workspace :current]) (let [pid (get-in state [:workspace :current])
selected (get-in state [:workspace pid :selected])] selected (get-in state [:workspace pid :selected])]
(prn "start-move-selected" selected)
(rx/from-coll (map start-move selected)))))) (rx/from-coll (map start-move selected))))))
(defn on-mouse-down (defn on-mouse-down
[event {:keys [id type] :as shape} selected] [event {:keys [id type] :as shape} selected]
(let [selected? (contains? selected id) (let [selected? (contains? selected id)
drawing? @refs/selected-drawing-tool] drawing? @refs/selected-drawing-tool]
(prn "on-mouse-down" id type selected? (= type :canvas))
(when-not (:blocked shape) (when-not (:blocked shape)
(cond (cond
drawing? drawing?

View file

@ -29,7 +29,6 @@
(common/on-mouse-down event shape selected)) (common/on-mouse-down event shape selected))
(on-double-click [event] (on-double-click [event]
(when selected? (when selected?
(prn "path-component$on-double-click")
(st/emit! (dw/start-edition-mode (:id shape)))))] (st/emit! (dw/start-edition-mode (:id shape)))))]
[:g.shape {:class (when selected? "selected") [:g.shape {:class (when selected? "selected")
:on-double-click on-double-click :on-double-click on-double-click

View file

@ -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 <niwi@niwi.nz>
(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 %))))

View file

@ -25,11 +25,34 @@
(def +animation-timeout+ 600) (def +animation-timeout+ 600)
;; --- Message Event ;; --- Main API
(declare hide) (declare hide)
(declare show)
(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 (defn show
[data] [data]
(reify (reify
@ -53,47 +76,19 @@
[v] [v]
(= ::show (ptk/type v))) (= ::show (ptk/type v)))
(defn error ;; --- Hide Event
[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
(defn hide (defn hide
[] []
(let [canceled? (volatile! {})]
(reify (reify
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(update state :message (update state :message assoc :state :hide))
(fn [v]
(if (nil? v)
(do (vreset! canceled? true) nil)
(assoc v :state :hide)))))
ptk/WatchEvent ptk/WatchEvent
(watch [_ state stream] (watch [_ state stream]
(if @canceled?
(rx/empty)
(->> (rx/of #(dissoc % :message)) (->> (rx/of #(dissoc % :message))
(rx/delay +animation-timeout+))))))) (rx/delay +animation-timeout+)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; UI Components ;; UI Components
@ -145,6 +140,7 @@
(mf/defc messages-widget (mf/defc messages-widget
[{:keys [message] :as props}] [{:keys [message] :as props}]
(prn "messages-widget" props)
(case (:type message) (case (:type message)
:error (mf/element notification-box props) :error (mf/element notification-box props)
:info (mf/element notification-box props) :info (mf/element notification-box props)

View file

@ -42,6 +42,12 @@
(s/def ::uuid uuid?) (s/def ::uuid uuid?)
(s/def ::email email?) (s/def ::email email?)
(s/def ::color color?) (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 ;; --- Public Api