0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-03-13 16:21:57 -05:00

Introduce cljs.spec and refactor all forms.

This commit is contained in:
Andrey Antukh 2016-11-13 15:42:44 +01:00
parent 29e6ebdb83
commit fce36cfdd9
No known key found for this signature in database
GPG key ID: 4DFEBCB8316A8B95
34 changed files with 1187 additions and 1144 deletions

View file

@ -6,19 +6,25 @@
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
(ns uxbox.main.data.auth
(:require [beicon.core :as rx]
(:require [cljs.spec :as s]
[beicon.core :as rx]
[uxbox.main.repo :as rp]
[uxbox.util.rstore :as rs]
[uxbox.util.router :as rt]
[uxbox.main.state :as st]
[uxbox.util.schema :as us]
[uxbox.util.spec :as us]
[uxbox.util.i18n :refer (tr)]
[uxbox.main.state :as st]
[uxbox.main.data.projects :as udp]
[uxbox.main.data.users :as udu]
[uxbox.main.data.messages :as udm]
[uxbox.main.data.forms :as udf]
[uxbox.util.storage :refer (storage)]))
(s/def ::username string?)
(s/def ::password string?)
(s/def ::fullname string?)
(s/def ::email us/email?)
(s/def ::token string?)
;; --- Logged In
(defrecord LoggedIn [data]
@ -58,8 +64,12 @@
(rx/map logged-in)
(rx/catch rp/client-error? on-error)))))
(s/def ::login-event
(s/keys :req-un [::username ::password]))
(defn login
[params]
{:pre [(us/valid? ::login-event params)]}
(map->Login params))
;; --- Logout
@ -80,40 +90,33 @@
;; --- Register
(defrecord Register [data]
;; TODO: clean form on success
(defrecord Register [data on-error]
rs/WatchEvent
(-apply-watch [_ state stream]
(letfn [(on-error [{payload :payload}]
(->> (:payload payload)
(udf/assign-errors :register)
(rx/of)))]
(letfn [(handle-error [{payload :payload}]
(on-error payload)
(rx/empty))]
(rx/merge
(->> (rp/req :auth/register data)
(rx/map :payload)
(rx/map (constantly ::registered))
(rx/catch rp/client-error? on-error))
(rx/catch rp/client-error? handle-error))
(->> stream
(rx/filter #(= % ::registered))
(rx/take 1)
(rx/map #(login data)))
(->> stream
(rx/filter logged-in?)
(rx/take 1)
(rx/map #(udf/clean :register)))))))
(rx/map #(login data)))))))
(def register-schema
{:username [us/required us/string]
:fullname [us/required us/string]
:email [us/required us/email]
:password [us/required us/string]})
(s/def ::register-event
(s/keys :req-un [::fullname ::username ::email ::password]))
(defn register
"Create a register event instance."
[data]
(let [[errors data] (us/validate data register-schema)]
(if errors
(udf/assign-errors :register errors)
(Register. data))))
[data on-error]
{:pre [(us/valid? ::register-event data)
(fn? on-error)]}
(Register. data on-error))
;; --- Recovery Request
@ -122,9 +125,7 @@
(-apply-watch [_ state stream]
(letfn [(on-error [{payload :payload}]
(println "on-error" payload)
(->> (:payload payload)
(udf/assign-errors :recovery-request)
(rx/of)))]
(rx/empty))]
(rx/merge
(->> (rp/req :auth/recovery-request data)
(rx/map (constantly ::recovery-requested))
@ -132,11 +133,14 @@
(->> stream
(rx/filter #(= % ::recovery-requested))
(rx/take 1)
(rx/do #(udm/info! (tr "auth.message.recovery-token-sent")))
(rx/map #(udf/clean :recovery-request)))))))
(rx/do #(udm/info! (tr "auth.message.recovery-token-sent"))))))))
(s/def ::recovery-request-event
(s/keys :req-un [::username]))
(defn recovery-request
[data]
{:pre [(us/valid? ::recovery-request-event data)]}
(RecoveryRequest. data))
;; --- Check Recovery Token
@ -153,8 +157,9 @@
(rx/catch rp/client-error? on-error)))))
(defn validate-recovery-token
[data]
(ValidateRecoveryToken. data))
[token]
{:pre [(string? token)]}
(ValidateRecoveryToken. token))
;; --- Recovery (Password)
@ -171,8 +176,10 @@
(rx/mapcat on-success)
(rx/catch rp/client-error? on-error)))))
(s/def ::recovery-event
(s/keys :req-un [::username ::token]))
(defn recovery
[{:keys [token password]}]
[{:keys [token password] :as data}]
{:pre [(us/valid? ::recovery-event data)]}
(Recovery. token password))

View file

@ -11,7 +11,7 @@
[uxbox.util.rstore :as rs]
[uxbox.util.router :as r]
[uxbox.main.state :as st]
[uxbox.util.schema :as sc]
[uxbox.util.forms :as sc]
[uxbox.main.repo :as rp]
[uxbox.main.data.projects :as dp]
[uxbox.main.data.colors :as dc]

View file

@ -1,89 +0,0 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) 2016 Andrey Antukh <niwi@niwi.nz>
(ns uxbox.main.data.forms
(:require [beicon.core :as rx]
[lentes.core :as l]
[uxbox.main.repo :as rp]
[uxbox.util.rstore :as rs]
[uxbox.main.state :as st]
[uxbox.util.schema :as sc]
[uxbox.util.i18n :refer (tr)]))
;; --- Assign Errors
(defrecord AssignErrors [type errors]
rs/UpdateEvent
(-apply-update [_ state]
(assoc-in state [:errors type] errors)))
(defn assign-errors
([type] (assign-errors type nil))
([type errors]
(AssignErrors. type errors)))
;; --- Assign Field Value
(defrecord AssignFieldValue [type field value]
rs/UpdateEvent
(-apply-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))))))
(defn assign-field-value
[type field value]
(AssignFieldValue. type field value))
;; --- Clean Errors
(defrecord CleanErrors [type]
rs/UpdateEvent
(-apply-update [_ state]
(assoc-in state [:errors type] nil)))
(defn clean-errors
[type]
(CleanErrors. type))
;; --- Clean Form
(defrecord CleanForm [type]
rs/UpdateEvent
(-apply-update [_ state]
(assoc-in state [:forms type] nil)))
(defn clean-form
[type]
(CleanForm. type))
;; --- Clean
(defrecord Clean [type]
rs/WatchEvent
(-apply-watch [_ state s]
(rx/of (clean-form type)
(clean-errors type))))
(defn clean
[type]
(Clean. type))
;; --- Helpers
(defn focus-form-data
[type]
(-> (l/in [:forms type])
(l/derive st/state)))
(defn focus-form-errors
[type]
(-> (l/in [:errors type])
(l/derive st/state)))

View file

@ -12,7 +12,7 @@
[uxbox.util.router :as r]
[uxbox.main.repo :as rp]
[uxbox.util.i18n :refer (tr)]
[uxbox.util.schema :as sc]
[uxbox.util.forms :as sc]
[uxbox.main.data.pages :as udp]
[uxbox.main.state :as st]
[uxbox.util.datetime :as dt]
@ -35,7 +35,7 @@
(rs/emit! (fetch-page-history id)
(fetch-pinned-page-history id)))]
(as-> rs/stream $
(rx/filter udp/page-synced? $)
(rx/filter udp/page-persisted? $)
(rx/delay 500 $)
(rx/map (comp :id :page) $)
(rx/on-value $ on-value))))
@ -137,7 +137,7 @@
rs/WatchEvent
(-apply-watch [_ state s]
(rx/of (udp/update-page id))))
(rx/of (udp/persist-page id))))
(defn apply-selected-history
[id]

View file

@ -3,21 +3,43 @@
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
(ns uxbox.main.data.pages
(:require [cuerdas.core :as str]
(:require [cljs.spec :as s]
[cuerdas.core :as str]
[beicon.core :as rx]
[lentes.core :as l]
[uxbox.main.repo :as rp]
[uxbox.main.state :as st]
[uxbox.util.spec :as us]
[uxbox.util.rstore :as rs]
[uxbox.util.router :as r]
[uxbox.main.repo :as rp]
[uxbox.util.i18n :refer (tr)]
[uxbox.util.schema :as sc]
[uxbox.main.state :as st]
[uxbox.util.forms :as sc]
[uxbox.util.datetime :as dt]
[uxbox.util.data :refer (without-keys replace-by-id)]))
;; --- Specs
(s/def ::id uuid?)
(s/def ::name string?)
(s/def ::project uuid?)
(s/def ::grid-x-axis number?)
(s/def ::grid-y-axis number?)
(s/def ::grid-color us/color?)
(s/def ::grid-alignment boolean?)
(s/def ::width number?)
(s/def ::height number?)
(s/def ::layout string?)
(s/def ::metadata
(s/keys :req-un [::width ::height]
:opt-un [::grid-y-axis
::grid-x-axis
::grid-color
::grid-alignment
::layout]))
;; --- Protocols
@ -99,75 +121,84 @@
[projectid]
(FetchPages. projectid))
;; --- Page Created
(defrecord PageCreated [data]
rs/UpdateEvent
(-apply-update [_ state]
(-> state
(assoc-page data)
(assoc-packed-page data))))
(s/def ::page-created-event
(s/keys :req-un [::id ::name ::project ::metadata]))
(defn page-created
[data]
{:pre [(us/valid? ::page-created-event data)]}
(PageCreated. data))
;; --- Create Page
(defrecord CreatePage [name project metadata]
(defrecord CreatePage [name project width height layout]
rs/WatchEvent
(-apply-watch [this state s]
(letfn [(on-created [{page :payload}]
(rx/of
#(assoc-page % page)
#(assoc-packed-page % page)))]
(let [params {:name name
:project project
:data {}
:metadata metadata}]
(->> (rp/req :create/page params)
(rx/mapcat on-created))))))
(let [params {:name name
:project project
:data {}
:metadata {:width width
:height height
:layout layout}}]
(->> (rp/req :create/page params)
(rx/map :payload)
(rx/map page-created)))))
(def ^:private create-page-schema
{:name [sc/required sc/string]
:metadata [sc/required]
:project [sc/required sc/uuid]})
(s/def ::create-page-event
(s/keys :req-un [::name ::project ::width ::height ::layout]))
(defn create-page
[data]
(-> (sc/validate! data create-page-schema)
(map->CreatePage)))
{:pre [(us/valid? ::create-page-event data)]}
(map->CreatePage data))
;; --- Page Synced
;; --- Page Persisted
(defrecord PageSynced [page]
(defrecord PagePersisted [data]
rs/UpdateEvent
(-apply-update [this state]
(-> state
(assoc-in [:pages (:id page) :version] (:version page))
(assoc-page page))))
(-apply-update [_ state]
(assoc-page state data)))
(defn- page-synced?
(defn- page-persisted?
[event]
(instance? PageSynced event))
(instance? PagePersisted event))
;; --- Sync Page
;; TODO: add specs
(defrecord SyncPage [id]
rs/WatchEvent
(-apply-watch [this state s]
(let [page (pack-page state id)]
(->> (rp/req :update/page page)
(rx/map (comp ->PageSynced :payload))))))
(defn page-persisted
[data]
{:pre [(map? data)]}
(PagePersisted. data))
(defn sync-page
[id]
(SyncPage. id))
;; --- Persist Page
;; --- Update Page
(defrecord UpdatePage [id]
(defrecord PersistPage [id]
rs/WatchEvent
(-apply-watch [this state s]
(let [page (get-in state [:pages id])]
(if (:history page)
(rx/empty)
(rx/of (sync-page id))))))
(let [page (pack-page state id)]
(->> (rp/req :update/page page)
(rx/map :payload)
(rx/map page-persisted)))))))
(defn update-page?
(defn persist-page?
[v]
(instance? UpdatePage v))
(instance? PersistPage v))
(defn update-page
(defn persist-page
[id]
(UpdatePage. id))
(PersistPage. id))
(defn watch-page-changes
"A function that starts watching for `IPageUpdate`
@ -182,59 +213,82 @@
(as-> rs/stream $
(rx/filter #(satisfies? IPageUpdate %) $)
(rx/debounce 1000 $)
(rx/on-next $ #(rs/emit! (update-page id)))))
(rx/on-next $ #(rs/emit! (persist-page id)))))
;; --- Update Page Metadata
;; --- Page Metadata Persisted
;; This is a simplified version of `UpdatePage` event
(defrecord MetadataPersisted [id data]
rs/UpdateEvent
(-apply-update [_ state]
;; TODO: page-data update
(assoc-in state [:pages id :version] (:version data))))
(s/def ::metadata-persisted-event
(s/keys :req-un [::id ::version]))
(defn metadata-persisted
[{:keys [id] :as data}]
{:pre [(us/valid? ::metadata-persisted-event data)]}
(MetadataPersisted. id data))
;; --- Persist Page Metadata
;; This is a simplified version of `PersistPage` event
;; that does not sends the heavyweiht `:data` attribute
;; and only serves for update other page data.
;; TODO: sync also with the pagedata-by-id index.
(defrecord UpdatePageMetadata [id name width height layout options]
rs/UpdateEvent
(-apply-update [_ state]
(letfn [(updater [page]
(merge page
(when options {:options options})
(when width {:width width})
(when height {:height height})
(when name {:name name})))]
(update-in state [:pages id] updater)))
(defrecord PersistMetadata [id]
rs/WatchEvent
(-apply-watch [this state s]
(letfn [(on-success [{page :payload}]
#(assoc-in % [:pages id :version] (:version page)))]
(->> (rp/req :update/page-metadata (into {} this))
(rx/map on-success)))))
(-apply-watch [_ state stream]
(let [page (get-in state [:pages id])]
(->> (rp/req :update/page-metadata page)
(rx/map :payload)
(rx/map metadata-persisted)))))
(def ^:private update-page-schema
{:id [sc/required]
:project [sc/required]
:version [sc/required]
:name [sc/required sc/string]
:metadata [sc/required]})
(defn update-page-metadata
[data]
(-> (sc/validate! data update-page-schema {:strip false})
(dissoc data :data)
(map->UpdatePageMetadata)))
(defn persist-metadata
[id]
{:pre [(uuid? id)]}
(PersistMetadata. id))
;; --- Update Page Options
(defrecord UpdatePageOptions [id options]
(defrecord UpdateMetadata [id metadata]
rs/UpdateEvent
(-apply-update [_ state]
(assoc-in state [:pages id :metadata] metadata))
rs/WatchEvent
(-apply-watch [this state s]
(let [page (get-in state [:pages id])
page (assoc page :options options)]
(rx/of (map->UpdatePageMetadata page)))))
(rx/of (persist-metadata id))))
(defn update-page-options
[id options]
(UpdatePageOptions. id options))
(defn update-metadata
[id metadata]
{:pre [(uuid? id) (us/valid? ::metadata metadata)]}
(UpdateMetadata. id metadata))
;; --- Update Page
(defrecord UpdatePage [id name width height layout]
rs/UpdateEvent
(-apply-update [this state]
(println "update-page" this)
(-> state
(assoc-in [:pages id :name] name)
(assoc-in [:pages id :metadata :width] width)
(assoc-in [:pages id :metadata :height] height)
(assoc-in [:pages id :metadata :layout] layout)))
rs/WatchEvent
(-apply-watch [_ state stream]
(rx/of (persist-metadata id))))
(s/def ::update-page-event
(s/keys :req-un [::name ::width ::height ::layout]))
(defn update-page
[id {:keys [name width height layout] :as data}]
{:pre [(uuid? id) (us/valid? ::update-page-event data)]}
(UpdatePage. id name width height layout))
;; --- Delete Page (by id)

View file

@ -14,7 +14,7 @@
[uxbox.util.rstore :as rs]
[uxbox.util.router :as r]
[uxbox.util.i18n :refer (tr)]
[uxbox.util.schema :as sc]
[uxbox.util.forms :as sc]
[uxbox.main.data.pages :as udp]))
;; --- Helpers
@ -25,6 +25,7 @@
(let [page {:id (:page-id project)
:name (:page-name project)
:version (:page-version project)
:project (:id project)
:data (:page-data project)
:created-at (:page-created-at project)
:modified-at (:page-modified-at project)
@ -176,7 +177,6 @@
:width [sc/required sc/integer]
:height [sc/required sc/integer]
:layout [sc/required sc/string]})
(defn create-project
[params]
(-> (sc/validate! params create-project-schema)

View file

@ -10,7 +10,7 @@
[uxbox.util.uuid :as uuid]
[uxbox.util.rstore :as rs]
[uxbox.util.router :as r]
[uxbox.util.schema :as sc]
[uxbox.util.forms :as sc]
[uxbox.util.workers :as uw]
[uxbox.main.constants :as c]
[uxbox.main.geom :as geom]
@ -594,6 +594,6 @@
(defn align-point
[point]
(let [message {:cmd :grid/align :point point}]
(let [message {:cmd :grid-align :point point}]
(->> (uw/ask! worker message)
(rx/map :point))))

View file

@ -5,15 +5,20 @@
;; Copyright (c) 2016 Andrey Antukh <niwi@niwi.nz>
(ns uxbox.main.data.users
(:require [beicon.core :as rx]
[uxbox.main.repo :as rp]
(:require [cljs.spec :as s]
[beicon.core :as rx]
[uxbox.util.rstore :as rs]
[uxbox.main.state :as st]
[uxbox.util.schema :as sc]
[uxbox.util.spec :as us]
[uxbox.util.i18n :refer (tr)]
[uxbox.main.data.forms :as udf]
[uxbox.main.state :as st]
[uxbox.main.repo :as rp]
[uxbox.main.data.messages :as udm]))
(s/def ::fullname string?)
(s/def ::email us/email?)
(s/def ::username string?)
(s/def ::theme string?)
;; --- Profile Fetched
(defrecord ProfileFetched [data]
@ -55,37 +60,31 @@
;; --- Update Profile
(defrecord UpdateProfile [data]
(defrecord UpdateProfile [data on-success on-error]
rs/WatchEvent
(-apply-watch [_ state s]
(letfn [(on-error [{payload :payload}]
(->> (:payload payload)
(udf/assign-errors :profile/main)
(rx/of)))]
(letfn [(handle-error [{payload :payload}]
(on-error payload)
(rx/empty))]
(->> (rp/req :update/profile data)
(rx/map :payload)
(rx/do on-success)
(rx/map profile-updated)
(rx/catch rp/client-error? on-error)))))
(rx/catch rp/client-error? handle-error)))))
(def update-profile-schema
{:fullname [sc/required sc/string]
:email [sc/required sc/email]
:username [sc/required sc/string]})
(s/def ::update-profile-event
(s/keys :req-un [::fullname ::email ::username ::theme]))
(defn update-profile
[data]
(let [[errors data] (sc/validate data update-profile-schema)]
(if errors
(udf/assign-errors :profile/main errors)
(UpdateProfile. data))))
[data on-success on-error]
{:pre [(us/valid? ::update-profile-event data)
(fn? on-error)
(fn? on-success)]}
(UpdateProfile. data on-success on-error))
;; --- Password Updated
(defrecord PasswordUpdated []
rs/UpdateEvent
(-apply-update [_ state]
(assoc-in state [:forms :profile/password] {}))
rs/EffectEvent
(-apply-effect [_ state]
(udm/info! (tr "settings.password-saved"))))
@ -99,28 +98,22 @@
(defrecord UpdatePassword [data]
rs/WatchEvent
(-apply-watch [_ state s]
(letfn [(on-error [{payload :payload}]
(->> (:payload payload)
(udf/assign-errors :profile/password)
(rx/of)))]
(let [params {:old-password (:old-password data)
:password (:password-1 data)}]
(let [params {:old-password (:old-password data)
:password (:password-1 data)}]
(->> (rp/req :update/profile-password params)
(rx/map password-updated)
(rx/catch rp/client-error? on-error))))))
(rx/map password-updated)))))
(def update-password-schema
[[:password-1 sc/required sc/string [sc/min-len 6]]
[:password-2 sc/required sc/string
[sc/identical-to :password-1 :message "errors.form.password-not-match"]]
[:old-password sc/required sc/string]])
(s/def ::password-1 string?)
(s/def ::password-2 string?)
(s/def ::old-password string?)
(s/def ::update-password-event
(s/keys :req-un [::password-1 ::password-2 ::old-password]))
(defn update-password
[data]
(let [[errors data] (sc/validate data update-password-schema)]
(if errors
(udf/assign-errors :profile/password errors)
(UpdatePassword. data))))
{:pre [(us/valid? ::update-password-event data)]}
(UpdatePassword. data))
;; --- Update Photo
@ -132,7 +125,7 @@
(rx/map fetch-profile))))
(defn update-photo
([file]
(UpdatePhoto. file (constantly nil)))
([file] (update-photo file (constantly nil)))
([file done]
{:pre [(us/file? file) (fn? done)]}
(UpdatePhoto. file done)))

View file

@ -5,11 +5,13 @@
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
(ns uxbox.main.data.workspace
(:require [beicon.core :as rx]
(:require [cljs.spec :as s]
[beicon.core :as rx]
[uxbox.util.uuid :as uuid]
[uxbox.main.constants :as c]
[uxbox.util.rstore :as rs]
[uxbox.util.schema :as sc]
[uxbox.util.spec :as us]
[uxbox.util.forms :as sc]
[uxbox.util.geom.point :as gpt]
[uxbox.util.workers :as uw]
[uxbox.main.state :as st]
@ -18,7 +20,6 @@
[uxbox.main.data.pages :as udp]
[uxbox.main.data.shapes :as uds]
[uxbox.main.data.shapes-impl :as shimpl]
[uxbox.main.data.forms :as udf]
[uxbox.main.data.lightbox :as udl]
[uxbox.main.data.history :as udh]
[uxbox.util.datetime :as dt]
@ -210,44 +211,32 @@
(-apply-watch [_ state s]
(let [page (get-in state [:pages id])
opts (:options page)
message {:cmd :grid/init
message {:cmd :grid-init
:width c/viewport-width
:height c/viewport-height
:x-axis (:grid/x-axis opts c/grid-x-axis)
:y-axis (:grid/y-axis opts c/grid-y-axis)}]
:x-axis (:grid-x-axis opts c/grid-x-axis)
:y-axis (:grid-y-axis opts c/grid-y-axis)}]
(rx/merge
(->> (uw/send! worker message)
(rx/map #(activate-flag :grid/indexed)))
(when (:grid/alignment opts)
(rx/of (activate-flag :grid/alignment)))))))
(rx/map #(activate-flag :grid-indexed)))
(when (:grid-alignment opts)
(rx/of (activate-flag :grid-alignment)))))))
(defn initialize-alignment-index
[id]
(InitializeAlignmentIndex. id))
;; --- Update Workspace Settings (Form)
;; --- Update Metadata
(defrecord SubmitWorkspaceSettings [id options]
;; Is a workspace aware wrapper over uxbox.data.pages/UpdateMetadata event.
(defrecord UpdateMetadata [id metadata]
rs/WatchEvent
(-apply-watch [_ state s]
(rx/of (udp/update-page-options id options)
(initialize-alignment-index id)
(udf/clean :workspace/settings)))
(rx/of (udp/update-metadata id metadata)
(initialize-alignment-index id))))
rs/EffectEvent
(-apply-effect [_ state]
(udl/close!)))
(def submit-workspace-settings-schema
{:grid/y-axis [sc/required sc/integer [sc/in-range 2 100]]
:grid/x-axis [sc/required sc/integer [sc/in-range 2 100]]
:grid/alignment [sc/boolean]
:grid/color [sc/required sc/color]})
(defn submit-workspace-settings
[id data]
(let [schema submit-workspace-settings-schema
[errors data] (sc/validate data schema)]
(if errors
(udf/assign-errors :workspace/settings errors)
(SubmitWorkspaceSettings. id data))))
(defn update-metadata
[id metadata]
{:pre [(uuid? id) (us/valid? ::udp/metadata metadata)]}
(UpdateMetadata. id metadata))

View file

@ -11,38 +11,23 @@
[uxbox.main.repo.impl :refer (request send!)]
[uxbox.util.transit :as t]))
(defn decode-page
[{:keys [data metadata] :as page}]
(merge page
(when data {:data (t/decode data)})
(when metadata {:metadata (t/decode metadata)})))
(defn decode-payload
[{:keys [payload] :as rsp}]
(if (sequential? payload)
(assoc rsp :payload (mapv decode-page payload))
(assoc rsp :payload (decode-page payload))))
(defmethod request :fetch/pages
[type data]
(let [params {:url (str url "/pages")
:method :get}]
(->> (send! params)
(rx/map decode-payload))))
(send! params)))
(defmethod request :fetch/pages-by-project
[type {:keys [project] :as params}]
(let [url (str url "/projects/" project "/pages")]
(->> (send! {:method :get :url url})
(rx/map decode-payload))))
(send! {:method :get :url url})))
(defmethod request :fetch/page-history
[type {:keys [page] :as params}]
(let [url (str url "/pages/" page "/history")
query (select-keys params [:max :since :pinned])
params {:method :get :url url :query query}]
(->> (send! params)
(rx/map decode-payload))))
(send! params)))
(defmethod request :delete/page
[_ id]
@ -51,41 +36,30 @@
:method :delete})))
(defmethod request :create/page
[type {:keys [data metadata] :as body}]
(let [body (assoc body
:data (t/encode data)
:metadata (t/encode metadata))
params {:url (str url "/pages")
[type body]
(let [params {:url (str url "/pages")
:method :post
:body body}]
(->> (send! params)
(rx/map decode-payload))))
(send! params)))
(defmethod request :update/page
[type {:keys [id data metadata] :as body}]
(let [body (assoc body
:data (t/encode data)
:metadata (t/encode metadata))
params {:url (str url "/pages/" id)
[type {:keys [id] :as body}]
(let [params {:url (str url "/pages/" id)
:method :put
:body body}]
(->> (send! params)
(rx/map decode-payload))))
(send! params)))
(defmethod request :update/page-history
[type {:keys [id page] :as data}]
(let [params {:url (str url "/pages/" page "/history/" id)
:method :put
:body data}]
(->> (send! params)
(rx/map decode-payload))))
(send! params)))
(defmethod request :update/page-metadata
[type {:keys [id metadata] :as body}]
(let [body (dissoc body :data)
body (assoc body :metadata (t/encode metadata))
params {:url (str url "/pages/" id "/metadata")
:method :put
:body body}]
(->> (send! params)
(rx/map decode-payload))))
(send! params)))

View file

@ -14,27 +14,15 @@
(defmethod request :fetch/projects
[type data]
(letfn [(decode-payload [{:keys [payload] :as response}]
(assoc response :payload (mapv decode-page payload)))
(decode-page [{:keys [page-metadata page-data] :as project}]
(assoc project
:page-metadata (t/decode page-metadata)
:page-data (t/decode page-data)))]
;; Obtain the list of projects and decode the embedded
;; page data in order to have it usable.
(->> (send! {:url (str url "/projects")
:method :get})
(rx/map decode-payload))))
;; Obtain the list of projects and decode the embedded
;; page data in order to have it usable.
(send! {:url (str url "/projects")
:method :get}))
(defmethod request :fetch/project-by-token
[_ token]
(letfn [(decode-pages [response]
(let [pages (->> (get-in response [:payload :pages])
(mapv pages/decode-page))]
(assoc-in response [:payload :pages] pages)))]
(->> (send! {:url (str url "/projects-by-token/" token)
:method :get})
(rx/map decode-pages))))
(send! {:url (str url "/projects-by-token/" token)
:method :get}))
(defmethod request :create/project
[_ data]

View file

@ -11,27 +11,18 @@
[uxbox.main.repo.impl :refer (request send!)]
[uxbox.util.transit :as t]))
(defn- decode-payload
[{:keys [payload] :as rsp}]
(let [metadata (:metadata payload)]
(assoc rsp :payload
(assoc payload :metadata (t/decode metadata)))))
(defmethod request :fetch/profile
[type _]
(let [url (str url "/profile/me")
params {:method :get :url url}]
(->> (send! params)
(rx/map decode-payload))))
(send! params)))
(defmethod request :update/profile
[type {:keys [metadata] :as body}]
(let [body (assoc body :metadata (t/encode metadata))
params {:url (str url "/profile/me")
[type body]
(let [params {:url (str url "/profile/me")
:method :put
:body body}]
(->> (send! params)
(rx/map decode-payload))))
(send! params)))
(defmethod request :update/profile-password
[type data]

View file

@ -138,6 +138,8 @@
(def routes
[["/auth/login" :auth/login]
["/auth/register" :auth/register]
["/auth/recovery/request" :auth/recovery-request]
["/auth/recovery/token/:token" :auth/recovery]
["/settings/profile" :settings/profile]
["/settings/password" :settings/password]
@ -145,7 +147,6 @@
["/dashboard/projects" :dashboard/projects]
["/dashboard/elements" :dashboard/elements]
["/dashboard/icons" :dashboard/icons]
["/dashboard/icons/:type/:id" :dashboard/icons]
["/dashboard/icons/:type" :dashboard/icons]

View file

@ -5,74 +5,22 @@
;; Copyright (c) 2016 Andrey Antukh <niwi@niwi.nz>
(ns uxbox.main.ui.auth.login
(:require [sablono.core :as html :refer-macros [html]]
[lentes.core :as l]
(:require [lentes.core :as l]
[cuerdas.core :as str]
[rum.core :as rum]
[uxbox.util.router :as rt]
[uxbox.main.state :as st]
[uxbox.util.dom :as dom]
[uxbox.util.rstore :as rs]
[uxbox.util.forms :as forms]
[uxbox.util.mixins :as mx :include-macros true]
[uxbox.main.state :as st]
[uxbox.main.data.auth :as da]
[uxbox.main.data.messages :as udm]
[uxbox.util.dom :as dom]
[uxbox.main.ui.icons :as i]
[uxbox.main.ui.messages :as uum]
[uxbox.main.ui.navigation :as nav]
[uxbox.util.mixins :as mx :include-macros true]))
[uxbox.main.ui.navigation :as nav]))
(defn- login-submit
[event local]
(dom/prevent-default event)
(let [form (:form @local)]
(rs/emit! (da/login {:username (:email form)
:password (:password form)}))))
(defn- login-submit-enabled?
[local]
(let [form (:form @local)]
(and (not (str/empty? (:email form "")))
(not (str/empty? (:password form ""))))))
(defn- login-field-change
[local field event]
(let [value (str/trim (dom/event->value event))]
(swap! local assoc-in [:form field] value)))
(defn- login-page-render
[own local]
(let [on-submit #(login-submit % local)
submit-enabled? (login-submit-enabled? local)
form (:form @local)]
(html
[:div.login
[:div.login-body
(uum/messages)
[:a i/logo]
[:form {:on-submit on-submit}
[:div.login-content
[:input.input-text
{:name "email"
:ref "email"
:value (:email form "")
:on-change #(login-field-change local :email %)
:placeholder "Email or Username"
:type "text"}]
[:input.input-text
{:name "password"
:ref "password"
:value (:password form "")
:on-change #(login-field-change local :password %)
:placeholder "Password"
:type "password"}]
[:input.btn-primary
{:name "login"
:class (when-not submit-enabled? "btn-disabled")
:disabled (not submit-enabled?)
:value "Continue"
:type "submit"}]
[:div.login-links
[:a {:on-click #(rt/go :auth/recovery-request)} "Forgot your password?"]
[:a {:on-click #(rt/go :auth/register)} "Don't have an account?"]]]]]])))
(def form-data (forms/focus-data :login st/state))
(def set-value! (partial forms/set-value! :login))
(defn- login-page-will-mount
[own]
@ -80,9 +28,54 @@
(rt/go :dashboard/projects))
own)
(def login-page
(mx/component
{:render #(login-page-render % (:rum/local %))
:will-mount login-page-will-mount
:name "login-page"
:mixins [(mx/local)]}))
(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+)]
(letfn [(on-change [event field]
(let [value (dom/event->value event)]
(set-value! field value)))
(on-submit [event]
(dom/prevent-default event)
(rs/emit! (da/login {:username (:email data)
:password (:password data)})))]
[:form {:on-submit on-submit}
[:div.login-content
[:input.input-text
{:name "email"
:ref "email"
:value (:email data "")
:on-change #(on-change % :email)
:placeholder "Email or Username"
:type "text"}]
[:input.input-text
{:name "password"
:ref "password"
:value (:password data "")
:on-change #(on-change % :password)
:placeholder "Password"
:type "password"}]
[:input.btn-primary
{:name "login"
:class (when-not valid? "btn-disabled")
:disabled (not valid?)
:value "Continue"
:type "submit"}]
[:div.login-links
[:a {:on-click #(rt/go :auth/recovery-request)} "Forgot your password?"]
[:a {:on-click #(rt/go :auth/register)} "Don't have an account?"]]]])))
(mx/defc login-page
{:mixins [mx/static]
:will-mount login-page-will-mount}
[]
[:div.login
[:div.login-body
(uum/messages)
[:a i/logo]
(login-form)]])

View file

@ -5,81 +5,59 @@
;; Copyright (c) 2016 Andrey Antukh <niwi@niwi.nz>
(ns uxbox.main.ui.auth.recovery
(:require [sablono.core :as html :refer-macros [html]]
[lentes.core :as l]
(:require [lentes.core :as l]
[cuerdas.core :as str]
[rum.core :as rum]
[uxbox.util.router :as rt]
[uxbox.main.state :as st]
[uxbox.util.rstore :as rs]
[uxbox.util.schema :as us]
[uxbox.util.forms :as forms]
[uxbox.util.mixins :as mx :include-macros true]
[uxbox.util.dom :as dom]
[uxbox.main.data.auth :as uda]
[uxbox.main.data.messages :as udm]
[uxbox.main.data.forms :as udf]
[uxbox.main.ui.forms :as forms]
[uxbox.main.ui.icons :as i]
[uxbox.main.ui.messages :as uum]
[uxbox.main.ui.navigation :as nav]
[uxbox.util.mixins :as mx :include-macros true]
[uxbox.util.dom :as dom]))
[uxbox.main.ui.navigation :as nav]))
;; --- Constants
;; --- Recovery Form
(def form-data
(-> (l/in [:forms :recovery])
(l/derive st/state)))
(def form-data (forms/focus-data :recovery st/state))
(def set-value! (partial forms/set-value! :recovery))
(def form-errors
(-> (l/in [:errors :recovery])
(l/derive st/state)))
(def +recovery-form+
{:password [forms/required forms/string]})
(def set-value!
(partial udf/assign-field-value :recovery))
;; --- Recovery Request Form
(def schema
{:password [us/required us/string]})
(defn- form-render
[own token]
(let [form (mx/react form-data)
errors (mx/react form-errors)
valid? (us/valid? form schema)]
(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+)]
(letfn [(on-change [field event]
(let [value (dom/event->value event)]
(rs/emit! (set-value! field value))))
(set-value! field value)))
(on-submit [event]
(dom/prevent-default event)
(rs/emit! (uda/recovery (assoc form :token token))))]
(html
[:form {:on-submit on-submit}
[:div.login-content
(rs/emit! (uda/recovery data)
(forms/clear :recovery)))]
[:form {:on-submit on-submit}
[:div.login-content
[:input.input-text
{:name "password"
:value (:password data "")
:on-change (partial on-change :password)
:placeholder "Password"
:type "password"}]
[:input.btn-primary
{:name "login"
:class (when-not valid? "btn-disabled")
:disabled (not valid?)
:value "Recover password"
:type "submit"}]
[:div.login-links
[:a {:on-click #(rt/go :auth/login)} "Go back!"]]]])))
[:input.input-text
{:name "password"
:value (:password form "")
:on-change (partial on-change :password)
:placeholder "Password"
:type "password"}]
(forms/input-error errors :password)
[:input.btn-primary
{:name "login"
:class (when-not valid? "btn-disabled")
:disabled (not valid?)
:value "Recover password"
:type "submit"}]
[:div.login-links
[:a {:on-click #(rt/go :auth/login)} "Go back!"]]]]))))
(def form
(mx/component
{:render form-render
:name "form"
:mixins [mx/static mx/reactive]}))
;; --- Recovery Request Page
;; --- Recovery Page
(defn- recovery-page-will-mount
[own]
@ -87,18 +65,13 @@
(rs/emit! (uda/validate-recovery-token token))
own))
(defn- recovery-page-render
[own token]
(html
[:div.login
[:div.login-body
(uum/messages)
[:a i/logo]
(form token)]]))
(def recovery-page
(mx/component
{:render recovery-page-render
:will-mount recovery-page-will-mount
:name "recovery-page"
:mixins [mx/static]}))
(mx/defc recovery-page
{:mixins [mx/static]
:will-mount recovery-page-will-mount
:will-unmount (forms/cleaner-fn :recovery)}
[token]
[:div.login
[:div.login-body
(uum/messages)
[:a i/logo]
(recovery-form token)]])

View file

@ -5,92 +5,63 @@
;; Copyright (c) 2016 Andrey Antukh <niwi@niwi.nz>
(ns uxbox.main.ui.auth.recovery-request
(:require [sablono.core :as html :refer-macros [html]]
[lentes.core :as l]
(:require [lentes.core :as l]
[cuerdas.core :as str]
[rum.core :as rum]
[uxbox.util.router :as rt]
[uxbox.main.state :as st]
[uxbox.util.rstore :as rs]
[uxbox.util.schema :as us]
[uxbox.util.forms :as forms]
[uxbox.util.mixins :as mx :include-macros true]
[uxbox.util.dom :as dom]
[uxbox.main.state :as st]
[uxbox.main.data.auth :as uda]
[uxbox.main.data.messages :as udm]
[uxbox.main.data.forms :as udf]
[uxbox.main.ui.forms :as forms]
[uxbox.main.ui.icons :as i]
[uxbox.main.ui.messages :as uum]
[uxbox.main.ui.navigation :as nav]
[uxbox.util.mixins :as mx :include-macros true]
[uxbox.util.dom :as dom]))
[uxbox.main.ui.navigation :as nav]))
;; --- Recovery Request Constants
(def form-data (forms/focus-data :recovery-request st/state))
(def set-value! (partial forms/set-value! :recovery-request))
(def form-data
(-> (l/in [:forms :recovery-request])
(l/derive st/state)))
(def +recovery-request-form+
{:username [forms/required forms/string]})
(def form-errors
(-> (l/in [:errors :recovery-request])
(l/derive st/state)))
(def set-value!
(partial udf/assign-field-value :recovery-request))
;; --- Recovery Request Form
(def schema
{:username [us/required us/string]})
(defn- form-render
[own]
(let [form (mx/react form-data)
errors (mx/react form-errors)
valid? (us/valid? form schema)]
(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]
(let [value (dom/event->value event)]
(rs/emit! (set-value! field value))))
(set-value! field value)))
(on-submit [event]
(dom/prevent-default event)
(rs/emit! (uda/recovery-request form)))]
(html
[:form {:on-submit on-submit}
[:div.login-content
[:input.input-text
{:name "username"
:value (:username form "")
:on-change (partial on-change :username)
:placeholder "username or email address"
:type "text"}]
(forms/input-error errors :username)
[:input.btn-primary
{:name "login"
:class (when-not valid? "btn-disabled")
:disabled (not valid?)
:value "Recover password"
:type "submit"}]
[:div.login-links
[:a {:on-click #(rt/go :auth/login)} "Go back!"]]]]))))
(def form
(mx/component
{:render form-render
:name "form"
:mixins [mx/static mx/reactive]}))
(rs/emit! (uda/recovery-request data)
(forms/clear :recovery-request)))]
[:form {:on-submit on-submit}
[:div.login-content
[:input.input-text
{:name "username"
:value (:username data "")
:on-change (partial on-change :username)
:placeholder "username or email address"
:type "text"}]
[:input.btn-primary
{:name "login"
:class (when-not valid? "btn-disabled")
:disabled (not valid?)
:value "Recover password"
:type "submit"}]
[:div.login-links
[:a {:on-click #(rt/go :auth/login)} "Go back!"]]]])))
;; --- Recovery Request Page
(defn- recovery-request-page-render
[own]
(html
[:div.login
[:div.login-body
(uum/messages)
[:a i/logo]
(form)]]))
(def recovery-request-page
(mx/component
{:render recovery-request-page-render
:name "recovery-request-page"
:mixins [mx/static]}))
(mx/defc recovery-request-page
{:mixins [mx/static]
:will-unmount (forms/cleaner-fn :recovery-request)}
[]
[:div.login
[:div.login-body
(uum/messages)
[:a i/logo]
(recovery-request-form)]])

View file

@ -5,116 +5,104 @@
;; Copyright (c) 2016 Andrey Antukh <niwi@niwi.nz>
(ns uxbox.main.ui.auth.register
(:require [sablono.core :as html :refer-macros [html]]
[lentes.core :as l]
(:require [lentes.core :as l]
[cuerdas.core :as str]
[rum.core :as rum]
[uxbox.util.router :as rt]
[uxbox.main.state :as st]
[uxbox.util.rstore :as rs]
[uxbox.util.schema :as us]
[uxbox.util.forms :as forms]
[uxbox.util.mixins :as mx :include-macros true]
[uxbox.util.dom :as dom]
[uxbox.main.state :as st]
[uxbox.main.data.auth :as uda]
[uxbox.main.data.messages :as udm]
[uxbox.main.data.forms :as udf]
[uxbox.main.ui.forms :as forms]
[uxbox.main.ui.icons :as i]
[uxbox.main.ui.messages :as uum]
[uxbox.main.ui.navigation :as nav]
[uxbox.util.mixins :as mx :include-macros true]
[uxbox.util.dom :as dom]))
;; --- Constants
(def form-data
(-> (l/in [:forms :register])
(l/derive st/state)))
(def form-errors
(-> (l/in [:errors :register])
(l/derive st/state)))
(def set-value!
(partial udf/assign-field-value :register))
[uxbox.main.ui.navigation :as nav]))
;; --- Register Form
(defn- register-form-render
[own]
(let [form (mx/react form-data)
(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! :register))
(def set-error! (partial forms/set-error! :register))
(def +register-form+
{:username [forms/required forms/string]
:fullname [forms/required forms/string]
:email [forms/required forms/email]
:password [forms/required forms/string]})
(mx/defc register-form
{:mixins [mx/static mx/reactive]}
[]
(let [data (mx/react form-data)
errors (mx/react form-errors)
valid? (us/valid? form uda/register-schema)]
valid? (forms/valid? data +register-form+)]
(letfn [(on-change [field event]
(let [value (dom/event->value event)]
(rs/emit! (set-value! field value))))
(set-value! field value)))
(on-error [{:keys [type code] :as payload}]
(case code
:uxbox.services.users/email-already-exists
(set-error! :email "Email already exists")
:uxbox.services.users/username-already-exists
(set-error! :username "Username already exists")))
(on-submit [event]
(dom/prevent-default event)
(rs/emit! (uda/register form)))]
(html
[:form {:on-submit on-submit}
[:div.login-content
[:input.input-text
{:name "fullname"
:value (:fullname form "")
:on-change (partial on-change :fullname)
:placeholder "Full Name"
:type "text"}]
(forms/input-error errors :fullname)
(rs/emit! (uda/register data on-error)))]
[:form {:on-submit on-submit}
[:div.login-content
[:input.input-text
{:name "fullname"
:value (:fullname data "")
:on-change (partial on-change :fullname)
:placeholder "Full Name"
:type "text"}]
(forms/input-error errors :fullname)
[:input.input-text
{:name "username"
:value (:username form "")
:on-change (partial on-change :username)
:placeholder "Username"
:type "text"}]
(forms/input-error errors :username)
[:input.input-text
{:name "username"
:value (:username data "")
:on-change (partial on-change :username)
:placeholder "Username"
:type "text"}]
(forms/input-error errors :username)
[:input.input-text
{:name "email"
:ref "email"
:value (:email form "")
:on-change (partial on-change :email)
:placeholder "Email"
:type "text"}]
(forms/input-error errors :email)
[:input.input-text
{:name "email"
:ref "email"
:value (:email data "")
:on-change (partial on-change :email)
:placeholder "Email"
:type "text"}]
(forms/input-error errors :email)
[:input.input-text
{:name "password"
:ref "password"
:value (:password form "")
:on-change (partial on-change :password)
:placeholder "Password"
:type "password"}]
(forms/input-error errors :password)
[:input.input-text
{:name "password"
:ref "password"
:value (:password data "")
:on-change (partial on-change :password)
:placeholder "Password"
:type "password"}]
(forms/input-error errors :password)
[:input.btn-primary
{:name "login"
:class (when-not valid? "btn-disabled")
:disabled (not valid?)
:value "Register"
:type "submit"}]
[:div.login-links
;; [:a {:on-click #(rt/go :auth/recover-password)} "Forgot your password?"]
[:a {:on-click #(rt/go :auth/login)} "Already have an account?"]]]]))))
(def register-form
(mx/component
{:render register-form-render
:name "register-form"
:mixins [mx/static mx/reactive]}))
[:input.btn-primary
{:name "login"
:class (when-not valid? "btn-disabled")
:disabled (not valid?)
:value "Register"
:type "submit"}]
[:div.login-links
;; [:a {:on-click #(rt/go :auth/recover-password)} "Forgot your password?"]
[:a {:on-click #(rt/go :auth/login)} "Already have an account?"]]]])))
;; --- Register Page
(defn- register-page-render
(mx/defc register-page
{:mixins [mx/static]}
[own]
(html
[:div.login
[:div.login-body
(uum/messages)
[:a i/logo]
(register-form)]]))
(def register-page
(mx/component
{:render register-page-render
:name "register-page"
:mixins [mx/static]}))
[:div.login
[:div.login-body
(uum/messages)
[:a i/logo]
(register-form)]])

View file

@ -9,7 +9,7 @@
[rum.core :as rum]
[lentes.core :as l]
[goog.events :as events]
[uxbox.util.schema :as sc]
[uxbox.util.forms :as sc]
[uxbox.util.mixins :as mx :include-macros true]
[uxbox.util.math :as mth]
[uxbox.util.data :as data]

View file

@ -15,7 +15,6 @@
[uxbox.main.ui.messages :as uum]
[uxbox.main.ui.colorpicker :refer (colorpicker)]
[uxbox.main.ui.dashboard.header :refer (header)]
[uxbox.main.ui.forms :as form]
[uxbox.main.ui.icons :as i]
[uxbox.main.ui.keyboard :as k]
[uxbox.main.ui.lightbox :as lbx]
@ -24,8 +23,7 @@
[uxbox.util.i18n :as t :refer (tr)]
[uxbox.util.lens :as ul]
[uxbox.util.mixins :as mx :include-macros true]
[uxbox.util.rstore :as rs]
[uxbox.util.schema :as sc]))
[uxbox.util.rstore :as rs]))
;; --- Refs

View file

@ -21,7 +21,7 @@
[uxbox.util.mixins :as mx :include-macros true]
[uxbox.util.datetime :as dt]
[uxbox.util.rstore :as rs]
[uxbox.util.schema :as sc]
[uxbox.util.forms :as sc]
[uxbox.util.lens :as ul]
[uxbox.util.i18n :refer (tr)]
[uxbox.util.dom :as dom]))

View file

@ -1,16 +0,0 @@
(ns uxbox.main.ui.forms
(:require [sablono.core :refer-macros [html]]
[uxbox.util.i18n :refer (tr)]
[uxbox.util.schema :as sc]))
(defn input-error
[errors field]
(when-let [error (get errors field)]
(html
[:ul.form-errors
[:li {:key error} (tr error)]])))
(defn error-class
[errors field]
(when (get errors field)
"invalid"))

View file

@ -6,98 +6,81 @@
;; Copyright (c) 2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
(ns uxbox.main.ui.settings.password
(:require [sablono.core :as html :refer-macros [html]]
[rum.core :as rum]
[lentes.core :as l]
(:require [lentes.core :as l]
[cuerdas.core :as str]
[uxbox.util.schema :as sc]
[uxbox.main.state :as st]
[uxbox.util.i18n :as t :refer (tr)]
[uxbox.util.router :as r]
[uxbox.util.rstore :as rs]
[uxbox.main.data.users :as udu]
[uxbox.main.data.forms :as udf]
[uxbox.main.ui.icons :as i]
[uxbox.main.ui.forms :as forms]
[uxbox.main.ui.messages :as uum]
[uxbox.util.forms :as forms]
[uxbox.util.dom :as dom]
[uxbox.util.mixins :as mx :include-macros true]
[uxbox.main.ui.settings.header :refer (header)]
[uxbox.util.dom :as dom]))
[uxbox.main.state :as st]
[uxbox.main.data.users :as udu]
[uxbox.main.ui.icons :as i]
[uxbox.main.ui.messages :as uum]
[uxbox.main.ui.settings.header :refer (header)]))
;; --- Password Form
(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! :profile-password))
(def set-errors! (partial forms/set-errors! :profile-password))
(def formdata
(-> (l/in [:forms :profile/password])
(l/derive st/state)))
(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]])
(def formerrors
(-> (l/in [:errors :profile/password])
(l/derive st/state)))
(def assign-field-value
(partial udf/assign-field-value :profile/password))
(defn password-form-render
[own]
(let [form (mx/react formdata)
errors (mx/react formerrors)
valid? (sc/valid? form udu/update-password-schema)]
(letfn [(on-field-change [field event]
(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+)]
(letfn [(on-change [field event]
(let [value (dom/event->value event)]
(rs/emit! (assign-field-value field value))))
(set-value! field value)))
(on-submit [event]
(rs/emit! (udu/update-password form)))]
(html
[:form.password-form
[:span.user-settings-label "Change password"]
[:input.input-text
{:type "password"
:class (forms/error-class errors :old-password)
:value (:old-password form "")
:on-change (partial on-field-change :old-password)
:placeholder "Old password"}]
(forms/input-error errors :old-password)
[:input.input-text
{:type "password"
:class (forms/error-class errors :password-1)
:value (:password-1 form "")
:on-change (partial on-field-change :password-1)
:placeholder "New password"}]
(forms/input-error errors :password-1)
[:input.input-text
{:type "password"
:class (forms/error-class errors :password-2)
:value (:password-2 form "")
:on-change (partial on-field-change :password-2)
:placeholder "Confirm password"}]
(forms/input-error errors :password-2)
[:input.btn-primary
{:type "button"
:class (when-not valid? "btn-disabled")
:disabled (not valid?)
:on-click on-submit
:value "Update settings"}]]))))
(def password-form
(mx/component
{:render password-form-render
:name "password-form"
:mixins [mx/static (mx/local) mx/reactive]}))
(println "on-submit" data)
#_(rs/emit! (udu/update-password form)))]
(println "password-form" data)
[: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)
:placeholder "Old password"}]
(forms/input-error errors :old-password)
[:input.input-text
{:type "password"
:class (forms/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)
[:input.input-text
{:type "password"
:class (forms/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)
[:input.btn-primary
{:type "button"
:class (when-not valid? "btn-disabled")
:disabled (not valid?)
:on-click on-submit
:value "Update settings"}]])))
;; --- Password Page
(defn password-page-render
[own]
(html
[:main.dashboard-main
(header)
(uum/messages)
[:section.dashboard-content.user-settings
[:section.user-settings-content
(password-form)]]]))
(def password-page
(mx/component
{:render password-page-render
:name "password-page"
:mixins [mx/static]}))
(mx/defc password-page
{:mixins [mx/static]}
[]
[:main.dashboard-main
(header)
(uum/messages)
[:section.dashboard-content.user-settings
[:section.user-settings-content
(password-form)]]])

View file

@ -6,100 +6,102 @@
;; Copyright (c) 2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
(ns uxbox.main.ui.settings.profile
(:require [sablono.core :as html :refer-macros [html]]
[rum.core :as rum]
[cuerdas.core :as str]
(:require [cuerdas.core :as str]
[lentes.core :as l]
[uxbox.util.schema :as sc]
[uxbox.util.forms :as forms]
[uxbox.util.router :as r]
[uxbox.main.state :as st]
[uxbox.util.rstore :as rs]
[uxbox.main.ui.icons :as i]
[uxbox.util.mixins :as mx :include-macros true]
[uxbox.main.ui.forms :as forms]
[uxbox.util.interop :refer (iterable->seq)]
[uxbox.util.dom :as dom]
[uxbox.main.state :as st]
[uxbox.main.ui.icons :as i]
[uxbox.main.ui.settings.header :refer (header)]
[uxbox.main.ui.messages :as uum]
[uxbox.main.data.users :as udu]
[uxbox.main.data.forms :as udf]
[uxbox.util.interop :refer (iterable->seq)]
[uxbox.util.dom :as dom]))
[uxbox.main.data.users :as udu]))
;; --- Constants
(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! :profile))
(def set-error! (partial forms/set-error! :profile))
(def formdata
(-> (l/in [:forms :profile/main])
(l/derive st/state)))
(def formerrors
(-> (l/in [:errors :profile/main])
(l/derive st/state)))
(def assign-field-value
(partial udf/assign-field-value :profile/main))
(def ^:private profile-ref
(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]})
;; --- Profile Form
(defn profile-form-render
[own]
(let [form (merge (mx/react profile-ref)
(mx/react formdata))
errors (mx/react formerrors)
valid? (sc/valid? form udu/update-profile-schema)
theme (get-in form [:metadata :theme] "light")]
(mx/defc profile-form
{:mixins [mx/static mx/reactive]
:will-unmount (forms/cleaner-fn :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+)
theme (:theme data)]
(letfn [(on-change [field event]
(let [value (dom/event->value event)]
(rs/emit! (assign-field-value field value))))
(set-value! field value)))
(on-error [{:keys [code] :as payload}]
(case code
:uxbox.services.users/email-already-exists
(set-error! :email "Email already exists")
:uxbox.services.users/username-already-exists
(set-error! :username "Username already exists")))
(on-success []
(forms/clear! :profile))
(on-submit [event]
(rs/emit! (udu/update-profile form)))]
(html
[:form.profile-form
[:span.user-settings-label "Name, username and email"]
[:input.input-text
{:type "text"
:on-change (partial on-change :fullname)
:value (:fullname form "")
:placeholder "Your name"}]
(forms/input-error errors :fullname)
[:input.input-text
{:type "text"
:on-change (partial on-change :username)
:value (:username form "")
:placeholder "Your username"}]
(rs/emit! (udu/update-profile data on-success on-error)))]
[:form.profile-form
[:span.user-settings-label "Name, username and email"]
[:input.input-text
{:type "text"
:on-change (partial on-change :fullname)
:value (:fullname data "")
:placeholder "Your name"}]
[:input.input-text
{:type "text"
:on-change (partial on-change :username)
:value (:username data "")
:placeholder "Your username"}]
(forms/input-error errors :username)
[:input.input-text
{:type "email"
:on-change (partial on-change :email)
:value (:email form "")
:value (:email data "")
:placeholder "Your email"}]
(forms/input-error errors :email)
[:span.user-settings-label "Choose a color theme"]
[:div.input-radio.radio-primary
[:input {:type "radio"
:checked (= theme "light")
:on-change (partial on-change [:metadata :theme])
:checked (when (= theme "light") "checked")
:on-change (partial on-change :theme)
:id "light-theme"
:name "theme"
:value "light"}]
[:label {:for "light-theme"} "Light theme"]
[:input {:type "radio"
:checked (= theme "dark")
:on-change (partial on-change [:metadata :theme])
:checked (when (= theme "dark") "checked")
:on-change (partial on-change :theme)
:id "dark-theme"
:name "theme"
:value "dark"}]
[:label {:for "dark-theme"} "Dark theme"]
[:input {:type "radio"
:checked (= theme "high-contrast")
:on-change (partial on-change [:metadata :theme])
:checked (when (= theme "high-contrast") "checked")
:on-change (partial on-change :theme)
:id "high-contrast-theme"
:name "theme"
:value "high-contrast"}]
@ -110,18 +112,13 @@
:class (when-not valid? "btn-disabled")
:disabled (not valid?)
:on-click on-submit
:value "Update settings"}]]))))
(def profile-form
(mx/component
{:render profile-form-render
:name "profile-form"
:mixins [(mx/local) mx/reactive mx/static]}))
:value "Update settings"}]])))
;; --- Profile Photo Form
(defn- profile-photo-form-render
[own]
(mx/defc profile-photo-form
{:mixins [mx/static mx/reactive]}
[]
(letfn [(on-change [event]
(let [target (dom/get-target event)
file (-> (dom/get-files target)
@ -133,36 +130,22 @@
photo (if (or (str/empty? photo) (nil? photo))
"images/avatar.jpg"
photo)]
(html
[:form.avatar-form
[:img {:src photo :border "0"}]
[:input {:type "file"
:value ""
:on-change on-change}]]))))
(def profile-photo-form
(mx/component
{:render profile-photo-form-render
:name profile-photo-form
:mixins [mx/static mx/reactive]}))
[:form.avatar-form
[:img {:src photo}]
[:input {:type "file"
:value ""
:on-change on-change}]])))
;; --- Profile Page
(defn profile-page-render
[own]
(html
[:main.dashboard-main
(header)
(uum/messages)
[:section.dashboard-content.user-settings
[:section.user-settings-content
[:span.user-settings-label "Your avatar"]
(profile-photo-form)
(profile-form)
]]]))
(def profile-page
(mx/component
{:render profile-page-render
:name "profile-page"
:mixins [mx/static]}))
(mx/defc profile-page
{:mixins [mx/static]}
[]
[:main.dashboard-main
(header)
(uum/messages)
[:section.dashboard-content.user-settings
[:section.user-settings-content
[:span.user-settings-label "Your avatar"]
(profile-photo-form)
(profile-form)]]])

View file

@ -61,8 +61,8 @@
(def alignment-ref
(letfn [(getter [flags]
(and (contains? flags :grid/indexed)
(contains? flags :grid/alignment)
(and (contains? flags :grid-indexed)
(contains? flags :grid-alignment)
(contains? flags :grid)))]
(-> (l/lens getter)
(l/derive flags-ref))))

View file

@ -21,16 +21,16 @@
(defn- grid-render
[own]
(let [options (:options (mx/react wb/page-ref))
color (:grid/color options "#cccccc")
color (:grid-color options "#cccccc")
width c/viewport-width
height c/viewport-height
x-ticks (range (- 0 c/canvas-start-x)
(- width c/canvas-start-x)
(:grid/x-axis options 10))
(:grid-x-axis options 10))
y-ticks (range (- 0 c/canvas-start-x)
(- height c/canvas-start-x)
(:grid/y-axis options 10))
(:grid-y-axis options 10))
path (as-> [] $
(reduce (partial vertical-line height) $ x-ticks)

View file

@ -6,119 +6,114 @@
;; Copyright (c) 2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
(ns uxbox.main.ui.workspace.settings
(:require [sablono.core :as html :refer-macros [html]]
[lentes.core :as l]
[rum.core :as rum]
(:require [lentes.core :as l]
[uxbox.main.constants :as c]
[uxbox.main.state :as st]
[uxbox.util.rstore :as rs]
[uxbox.main.data.pages :as udp]
[uxbox.main.data.forms :as udf]
[uxbox.main.data.workspace :as udw]
[uxbox.main.data.lightbox :as udl]
[uxbox.main.ui.icons :as i]
[uxbox.util.forms :as forms]
[uxbox.util.mixins :as mx :include-macros true]
[uxbox.main.ui.forms :as forms]
[uxbox.main.ui.lightbox :as lbx]
[uxbox.main.ui.colorpicker :as uucp]
[uxbox.main.ui.workspace.base :as wb]
[uxbox.util.dom :as dom]
[uxbox.util.data :refer (parse-int)]))
;; --- Lentes
(def formdata (udf/focus-form-data :workspace/settings))
(def formerrors (udf/focus-form-errors :workspace/settings))
(def assign-field-value (partial udf/assign-field-value :workspace/settings))
(def form-data (forms/focus-data :workspace-settings st/state))
(def form-errors (forms/focus-errors :workspace-settings st/state))
(def set-value! (partial forms/set-value! :workspace-settings))
(def set-errors! (partial forms/set-errors! :workspace-settings))
(def page-ref wb/page-ref)
;; --- Form Component
(def settings-form-defaults
{:grid/x-axis c/grid-x-axis
:grid/y-axis c/grid-y-axis
:grid/color "#b5bdb9"
:grid/alignment false})
(def +settings-defaults+
{:grid-x-axis c/grid-x-axis
:grid-y-axis c/grid-y-axis
:grid-color "#b5bdb9"
:grid-alignment false})
(defn- settings-form-render
[own]
(let [page (mx/react wb/page-ref)
form (merge settings-form-defaults
(:options page)
(mx/react formdata))
errors (mx/react formerrors)]
(def +settings-form+
{:grid-y-axis [forms/required forms/integer [forms/in-range 2 100]]
:grid-x-axis [forms/required forms/integer [forms/in-range 2 100]]
:grid-alignment [forms/boolean]
:grid-color [forms/required forms/color]})
(mx/defc settings-form
{:mixins [mx/reactive]}
[]
(let [{:keys [id] :as page} (mx/react page-ref)
errors (mx/react form-errors)
data (merge +settings-defaults+
(:metadata page)
(mx/react form-data))]
(letfn [(on-field-change [field event]
(let [value (dom/event->value event)
value (parse-int value "")]
(rs/emit! (assign-field-value field value))))
(set-value! field value)))
(on-color-change [color]
(rs/emit! (assign-field-value :grid/color color)))
(set-value! :grid-color color))
(on-align-change [event]
(let [checked? (-> (dom/get-target event)
(dom/checked?))]
(rs/emit! (assign-field-value :grid/alignment checked?))))
(set-value! :grid-alignment checked?)))
(on-submit [event]
(dom/prevent-default event)
(rs/emit! (udw/submit-workspace-settings (:id page) form)))]
(html
[:form {:on-submit on-submit}
[:span.lightbox-label "Grid size"]
[:div.project-size
[:div.input-element.pixels
[:input#grid-x.input-text
{:placeholder "X"
:type "number"
:class (forms/error-class errors :grid/x-axis)
:value (:grid/x-axis form "")
:on-change (partial on-field-change :grid/x-axis)
:min 1
:max 100}]]
[:div.input-element.pixels
[:input#grid-y.input-text
{:placeholder "Y"
:type "number"
:class (forms/error-class errors :grid/y-axis)
:value (:grid/y-axis form "")
:on-change (partial on-field-change :grid/y-axis)
:min 1
:max 100}]]]
[:span.lightbox-label "Grid color"]
(uucp/colorpicker
:value (:grid/color form)
:on-change on-color-change)
[:span.lightbox-label "Grid magnet option"]
[:div.input-checkbox.check-primary
[:input
{:type "checkbox"
:on-change on-align-change
:checked (:grid/alignment form)
:id "magnet"
:value "Yes"}]
[:label {:for "magnet"} "Activate magnet"]]
[:input.btn-primary
{:type "submit"
:value "Save"}]]))))
(let [[errors data] (forms/validate data +settings-form+)]
(if errors
(set-errors! errors)
(rs/emit! (udw/update-metadata id data)
(forms/clear :workspace-settings)
(udl/hide-lightbox)))))]
[:form {:on-submit on-submit}
[:span.lightbox-label "Grid size"]
[:div.project-size
[:div.input-element.pixels
[:input#grid-x.input-text
{:placeholder "X"
:type "number"
:class (forms/error-class errors :grid-x-axis)
:value (:grid-x-axis data "")
:on-change (partial on-field-change :grid-x-axis)
:min 2
:max 100}]]
[:div.input-element.pixels
[:input#grid-y.input-text
{:placeholder "Y"
:type "number"
:class (forms/error-class errors :grid-y-axis)
:value (:grid-y-axis data "")
:on-change (partial on-field-change :grid-y-axis)
:min 2
:max 100}]]]
[:span.lightbox-label "Grid color"]
(uucp/colorpicker
:value (:grid-color data)
:on-change on-color-change)
[:span.lightbox-label "Grid magnet option"]
[:div.input-checkbox.check-primary
[:input
{:type "checkbox"
:on-change on-align-change
:checked (:grid-alignment data)
:id "magnet"
:value "Yes"}]
[:label {:for "magnet"} "Activate magnet"]]
[:input.btn-primary
{:type "submit"
:value "Save"}]])))
(def settings-form
(mx/component
{:render settings-form-render
:name "settings-form"
:mixins [(mx/local) mx/reactive mx/static]}))
(defn- settings-dialog-render
(mx/defc settings-dialog
[own]
(html
[:div.lightbox-body.settings
[:h3 "Grid settings"]
(settings-form)
[:a.close {:href "#"
:on-click #(do (dom/prevent-default %)
(udl/close!))} i/close]]))
(def settings-dialog
(mx/component
{:render settings-dialog-render
:name "settings-dialog"
:mixins []}))
[:div.lightbox-body.settings
[:h3 "Grid settings"]
(settings-form)
[:a.close {:href "#"
:on-click #(do (dom/prevent-default %)
(udl/close!))} i/close]])
(defmethod lbx/render-lightbox :settings
[_]

View file

@ -16,12 +16,11 @@
[uxbox.main.data.pages :as udp]
[uxbox.main.data.workspace :as dw]
[uxbox.main.data.lightbox :as udl]
[uxbox.main.ui.dashboard.projects :refer (+layouts+)]
[uxbox.main.ui.workspace.base :as wb]
[uxbox.main.ui.workspace.sidebar.sitemap-pageform]
[uxbox.main.ui.icons :as i]
[uxbox.util.mixins :as mx :include-macros true]
[uxbox.main.ui.lightbox :as lbx]
[uxbox.util.data :refer (deep-merge parse-int)]
[uxbox.util.dom :as dom]))
;; --- Refs
@ -87,103 +86,3 @@
:let [active? (= (:id page) (:id current))]]
(-> (page-item page (count pages) active?)
(mx/with-key (:id page))))]]]))
;; --- Lightbox
(def +page-defaults+
{:width 1920
:height 1080
:layout :desktop})
(mx/defc layout-input
[local page id]
(let [layout (get +layouts+ id)
metadata (:metadata page)
size (select-keys layout [:width :height])
change #(swap! local update :metadata merge {:layout id} size)]
[:div
[:input {:type "radio"
:key id :id id
:name "project-layout"
:value (:id layout)
:checked (= id (:layout metadata))
:on-change change}]
[:label {:value (:id layout) :for id} (:name layout)]]))
(mx/defcs page-form-lightbox
{:mixins [(mx/local)]}
[own page]
(let [local (:rum/local own)
page (deep-merge page @local {:data nil})
metadata (:metadata page)
edition? (:id page)
valid? (and (not (str/empty? (str/trim (:name page ""))))
(pos? (:width metadata))
(pos? (:height metadata)))]
(letfn [(update-size [field e]
(let [value (dom/event->value e)
value (parse-int value)]
(swap! local assoc-in [:metadata field] value)))
(update-name [e]
(let [value (dom/event->value e)]
(swap! local assoc :name value)))
(toggle-sizes []
(let [width (get-in page [:metadata :width])
height (get-in page [:metadata :height])]
(swap! local update :metadata merge {:width height
:height width})))
(cancel [e]
(dom/prevent-default e)
(udl/close!))
(persist [e]
(dom/prevent-default e)
(udl/close!)
(if edition?
(rs/emit! (udp/update-page-metadata page))
(rs/emit! (udp/create-page page))))]
[:div.lightbox-body
(if edition?
[:h3 "Edit page"]
[:h3 "New page"])
[:form
[:input#project-name.input-text
{:placeholder "Page name"
:type "text"
:value (:name page "")
:auto-focus true
:on-change update-name}]
[:div.project-size
[:div.input-element.pixels
[:input#project-witdh.input-text
{:placeholder "Width"
:type "number"
:min 0
:max 4000
:value (:width metadata)
:on-change #(update-size :width %)}]]
[:a.toggle-layout {:on-click toggle-sizes} i/toggle]
[:div.input-element.pixels
[:input#project-height.input-text
{:placeholder "Height"
:type "number"
:min 0
:max 4000
:value (:height metadata)
:on-change #(update-size :height %)}]]]
[:div.input-radio.radio-primary
(layout-input local page "mobile")
(layout-input local page "tablet")
(layout-input local page "notebook")
(layout-input local page "desktop")]
(when valid?
[:input#project-btn.btn-primary
{:value "Go go go!"
:on-click persist
:type "button"}])]
[:a.close {:on-click cancel} i/close]])))
(defmethod lbx/render-lightbox :page-form
[{:keys [page]}]
(page-form-lightbox page))

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-2016 Andrey Antukh <niwi@niwi.nz>
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
(ns uxbox.main.ui.workspace.sidebar.sitemap-pageform
(:require [lentes.core :as l]
[cuerdas.core :as str]
[uxbox.main.state :as st]
[uxbox.main.data.pages :as udp]
[uxbox.main.data.workspace :as dw]
[uxbox.main.data.lightbox :as udl]
[uxbox.main.ui.dashboard.projects :refer (+layouts+)]
[uxbox.main.ui.icons :as i]
[uxbox.main.ui.lightbox :as lbx]
[uxbox.util.i18n :refer (tr)]
[uxbox.util.router :as r]
[uxbox.util.rstore :as rs]
[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]))
(def form-data (forms/focus-data :workspace-page-form st/state))
(def set-value! (partial forms/set-value! :workspace-page-form))
;; --- Lightbox
(def +page-defaults+
{:width 1920
:height 1080
:layout :desktop})
(def +page-form+
{:name [forms/required forms/string]
:width [forms/required forms/number]
:height [forms/required forms/number]
:layout [forms/required forms/string]})
(mx/defc layout-input
[data id]
(let [{:keys [id name width height]} (get +layouts+ id)]
(letfn [(on-change [event]
(set-value! :layout id)
(set-value! :width width)
(set-value! :height height))]
[:div
[:input {:type "radio"
:id id
:name "project-layout"
:value id
:checked (when (= id (:layout data)) "checked")
:on-change on-change}]
[:label {:value id :for id} name]])))
(mx/defc page-form
{:mixins [mx/static mx/reactive]}
[{:keys [metadata id] :as page}]
(let [data (merge +page-defaults+
(select-keys page [:name])
(select-keys metadata [:width :height :layout])
(mx/react form-data))
valid? (forms/valid? data +page-form+)]
(letfn [(update-size [field e]
(let [value (dom/event->value e)
value (parse-int value)]
(set-value! field value)))
(update-name [e]
(let [value (dom/event->value e)]
(set-value! :name value)))
(toggle-sizes []
(let [{:keys [width height]} data]
(set-value! :width height)
(set-value! :height width)))
(on-cancel [e]
(dom/prevent-default e)
(udl/close!))
(on-save [e]
(dom/prevent-default e)
(udl/close!)
(if (nil? id)
(rs/emit! (udp/create-page data))
(rs/emit! (udp/update-page id data))))]
[:form
[:input#project-name.input-text
{:placeholder "Page name"
:type "text"
:value (:name data "")
:auto-focus true
:on-change update-name}]
[:div.project-size
[:div.input-element.pixels
[:input#project-witdh.input-text
{:placeholder "Width"
:type "number"
:min 0
:max 4000
:value (:width data)
:on-change #(update-size :width %)}]]
[:a.toggle-layout {:on-click toggle-sizes} i/toggle]
[:div.input-element.pixels
[:input#project-height.input-text
{:placeholder "Height"
:type "number"
:min 0
:max 4000
:value (:height data)
:on-change #(update-size :height %)}]]]
[:div.input-radio.radio-primary
(layout-input data "mobile")
(layout-input data "tablet")
(layout-input data "notebook")
(layout-input data "desktop")]
(when valid?
[:input#project-btn.btn-primary
{:value "Go go go!"
:on-click on-save
:type "button"}])])))
(mx/defc page-form-lightbox
{:mixins [mx/static]
:will-unmount (fn [own]
(forms/clear! :workspace-page-form)
own)}
[{:keys [id] :as page}]
(letfn [(on-cancel [event]
(dom/prevent-default event)
(udl/close!))]
(let [creation? (nil? id)]
[:div.lightbox-body
(if creation?
[:h3 "New page"]
[:h3 "Edit page"])
(page-form page)
[:a.close {:on-click on-cancel} i/close]])))
(defmethod lbx/render-lightbox :page-form
[{:keys [page]}]
(page-form-lightbox page))

264
src/uxbox/util/forms.cljs Normal file
View file

@ -0,0 +1,264 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
(ns uxbox.util.forms
(:refer-clojure :exclude [keyword uuid vector boolean map set])
(:require [struct.core :as st]
[lentes.core :as l]
[beicon.core :as rx]
[uxbox.util.rstore :as rs]
[uxbox.util.mixins :as mx :include-macros true]
[uxbox.util.i18n :refer (tr)]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Form Validation
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; --- Form Validators
(def required
(assoc st/required :message "errors.form.required"))
(def string
(assoc st/string :message "errors.form.string"))
(def number
(assoc st/number :message "errors.form.number"))
(def integer
(assoc st/integer :message "errors.form.integer"))
(def boolean
(assoc st/boolean :message "errors.form.bool"))
(def identical-to
(assoc st/identical-to :message "errors.form.identical-to"))
(def in-range st/in-range)
;; (def uuid-like st/uuid-like)
(def uuid st/uuid)
(def keyword st/keyword)
(def integer-str st/integer-str)
(def number-str st/number-str)
;; (def boolean-like st/boolean-like)
(def email st/email)
;; (def function st/function)
(def positive st/positive)
;; (def validate st/validate)
;; (def validate! st/validate!)
(def max-len
{:message "errors.form.max-len"
:optional true
:validate (fn [v n]
(let [len (count v)]
(>= len v)))})
(def min-len
{:message "errors.form.min-len"
:optional true
:validate (fn [v n]
(>= (count v) n))})
(def color
{:message "errors.form.color"
:optional true
:validate #(not (nil? (re-find #"^#[0-9A-Fa-f]{6}$" %)))})
;; --- Public Validation Api
(defn validate
([data schema]
(validate data schema nil))
([data schema opts]
(st/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))))
(defn valid?
[data schema]
(let [[errors data] (validate data schema)]
(not errors)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Form Events
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; --- Set Error
(defrecord SetError [type field error]
rs/UpdateEvent
(-apply-update [_ state]
(assoc-in state [:errors type field] error)))
(defn set-error
([type field]
(set-error type field nil))
([type field error]
{:pre [(keyword? type)
(keyword? field)
(any? error)]}
(SetError. type field error)))
(defn set-error!
[& args]
(rs/emit! (apply set-error args)))
;; --- Set Errors
(defrecord SetErrors [type errors]
rs/UpdateEvent
(-apply-update [_ state]
(assoc-in state [:errors type] errors)))
(defn set-errors
([type]
(set-errors type nil))
([type errors]
{:pre [(keyword? type)
(or (map? errors)
(nil? errors))]}
(SetErrors. type errors)))
(defn set-errors!
[& args]
(rs/emit! (apply set-errors args)))
;; --- Set Value
(defrecord SetValue [type field value]
rs/UpdateEvent
(-apply-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))))))
(defn set-value
[type field value]
{:pre [(keyword? type)
(keyword? field)
(any? value)]}
(SetValue. type field value))
(defn set-value!
[type field value]
(rs/emit! (set-value type field value)))
;; --- Validate Form
;; (defrecord ValidateForm [type form data on-success]
;; rs/WatchEvent
;; (-apply-watch [_ state stream]
;; (let [[errors data] (validate data form)]
;; (if errors
;; (rx/of (set-errors type errors))
;; (do
;; (on-success data)
;; (rx/empty))))))
;; (defn validate-form
;; [& {:keys [type form data on-success]}]
;; {:pre [(keyword? type)
;; (map? form)
;; (map? data)
;; (fn? on-success)]}
;; (ValidateForm. type form data on-success))
;; (defn validate-form!
;; [& args]
;; (rs/emit! (apply validate-form args)))
;; --- Clear Form
(defrecord ClearForm [type]
rs/UpdateEvent
(-apply-update [_ state]
(assoc-in state [:forms type] nil)))
(defn clear-form
[type]
{:pre [(keyword? type)]}
(ClearForm. type))
(defn clear-form!
[type]
(rs/emit! (clear-form type)))
;; --- Clear Form
(defrecord ClearErrors [type]
rs/UpdateEvent
(-apply-update [_ state]
(assoc-in state [:errors type] nil)))
(defn clear-errors
[type]
{:pre [(keyword? type)]}
(ClearErrors. type))
(defn clear-errors!
[type]
(rs/emit! (clear-errors type)))
;; --- Clear
(defrecord Clear [type]
rs/WatchEvent
(-apply-watch [_ state s]
(rx/of (clear-form type)
(clear-errors type))))
(defn clear
[type]
(Clear. type))
(defn clear!
[type]
(rs/emit! (clear type)))
;; --- Helpers
(defn focus-data
[type state]
(-> (l/in [:forms type])
(l/derive state)))
(defn focus-errors
[type state]
(-> (l/in [:errors type])
(l/derive state)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Form UI
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(mx/defc input-error
[errors field]
(when-let [error (get errors field)]
[:ul.form-errors
[:li {:key error} (tr error)]]))
(defn error-class
[errors field]
(when (get errors field)
"invalid"))
(defn cleaner-fn
[type]
{:pre [(keyword? type)]}
(fn [own]
(clear! type)
own))

View file

@ -37,10 +37,7 @@
(defrecord Navigate [id params]
rs/EffectEvent
(-apply-effect [_ state]
(let [loc (merge {:handler id}
(when params
{:route-params params}))]
(r/navigate! +router+ id params))))
(r/navigate! +router+ id {})))
(defn navigate
([id] (navigate id nil))

View file

@ -1,85 +0,0 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
(ns uxbox.util.schema
(:refer-clojure :exclude [keyword uuid vector boolean map set])
(:require [struct.core :as st]
[uxbox.util.i18n :refer (tr)]))
;; (def datetime
;; {:message "must be an instant"
;; :optional true
;; :validate #(instance? Instant %)})
(def required
(assoc st/required :message "errors.form.required"))
(def string
(assoc st/string :message "errors.form.string"))
(def number
(assoc st/number :message "errors.form.number"))
(def integer
(assoc st/integer :message "errors.form.integer"))
(def boolean
(assoc st/boolean :message "errors.form.bool"))
(def identical-to
(assoc st/identical-to :message "errors.form.identical-to"))
(def in-range st/in-range)
;; (def uuid-like st/uuid-like)
(def uuid st/uuid)
(def keyword st/keyword)
(def integer-str st/integer-str)
(def number-str st/number-str)
;; (def boolean-like st/boolean-like)
(def email st/email)
;; (def function st/function)
;; (def positive st/positive)
;; (def validate st/validate)
;; (def validate! st/validate!)
(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}$" %)))})
(defn validate
([data schema]
(validate data schema nil))
([data schema opts]
(st/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))))
(defn valid?
[data schema]
(let [[errors data] (validate data schema)]
(not errors)))

47
src/uxbox/util/spec.cljs Normal file
View file

@ -0,0 +1,47 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
(ns uxbox.util.spec
(:require [cljs.spec :as s]))
;; --- Constants
(def email-rx
#"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$")
(def uuid-rx
#"^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$")
;; --- Predicates
(defn email?
[v]
(and string?
(re-matches email-rx v)))
(defn color?
[v]
(and (string? v)
(re-matches #"^#[0-9A-Fa-f]{6}$" v)))
(defn file?
[v]
(instance? js/File v))
;; --- Default Specs
(s/def ::uuid uuid?)
(s/def ::email email?)
(s/def ::color color?)
;; --- Public Api
(defn valid?
[spec data]
(let [valid (s/valid? spec data)]
(when-not valid
(js/console.error (str "Spec validation error:\n" (s/explain-str spec data))))
valid))

View file

@ -8,7 +8,7 @@
(:require [beicon.core :as rx]
[uxbox.util.rstore :as rs]
[uxbox.util.router :as rt]
[uxbox.util.schema :as sc]
[uxbox.util.forms :as sc]
[uxbox.util.data :refer (parse-int)]
[uxbox.main.repo :as rp]
[uxbox.main.data.pages :as udpg]

View file

@ -13,13 +13,13 @@
(defonce tree (kd/create))
(defmethod impl/handler :grid/init
(defmethod impl/handler :grid-init
[{:keys [sender width height x-axis y-axis] :as opts}]
(time
(kd/setup! tree width height (or x-axis 10) (or y-axis 10)))
(impl/reply! sender nil))
(defmethod impl/handler :grid/align
(defmethod impl/handler :grid-align
[{:keys [sender point] :as message}]
(let [point [(:x point) (:y point)]
results (kd/nearest tree point 1)