diff --git a/frontend/deps.edn b/frontend/deps.edn index 8a79a52f6..6e9fbcabd 100644 --- a/frontend/deps.edn +++ b/frontend/deps.edn @@ -7,10 +7,11 @@ environ/environ {:mvn/version "1.1.0"} metosin/reitit-core {:mvn/version "0.3.9"} + funcool/struct {:mvn/version "1.4.0"} funcool/beicon {:mvn/version "5.1.0"} funcool/cuerdas {:mvn/version "2.2.0"} funcool/lentes {:mvn/version "1.3.0-SNAPSHOT"} - funcool/potok {:mvn/version "2.4.0"} + funcool/potok {:mvn/version "2.5.0"} funcool/promesa {:mvn/version "3.0.0-SNAPSHOT"} funcool/rumext {:mvn/version "2.0.0-SNAPSHOT"} } diff --git a/frontend/src/uxbox/main/data/pages.cljs b/frontend/src/uxbox/main/data/pages.cljs index 67592c34e..fd9e3051d 100644 --- a/frontend/src/uxbox/main/data/pages.cljs +++ b/frontend/src/uxbox/main/data/pages.cljs @@ -9,13 +9,13 @@ [beicon.core :as rx] [cljs.spec.alpha :as s] [cuerdas.core :as str] - [lentes.core :as l] [potok.core :as ptk] [uxbox.main.repo :as rp] [uxbox.main.store :as st] + [uxbox.util.data :refer [index-by-id]] [uxbox.util.spec :as us] [uxbox.util.timers :as ts] - [uxbox.util.data :refer [index-by-id]])) + [uxbox.util.uuid :as uuid])) ;; --- Specs diff --git a/frontend/src/uxbox/main/data/projects.cljs b/frontend/src/uxbox/main/data/projects.cljs index df1336159..9e24636cf 100644 --- a/frontend/src/uxbox/main/data/projects.cljs +++ b/frontend/src/uxbox/main/data/projects.cljs @@ -12,6 +12,7 @@ [uxbox.main.store :as st] [uxbox.main.repo :as rp] [uxbox.main.data.pages :as udp] + [uxbox.util.uuid :as uuid] [uxbox.util.spec :as us] [uxbox.util.time :as dt] [uxbox.util.router :as rt])) diff --git a/frontend/src/uxbox/main/ui/auth/login.cljs b/frontend/src/uxbox/main/ui/auth/login.cljs index 9c1c21a63..039bf2044 100644 --- a/frontend/src/uxbox/main/ui/auth/login.cljs +++ b/frontend/src/uxbox/main/ui/auth/login.cljs @@ -7,45 +7,27 @@ (ns uxbox.main.ui.auth.login (:require - [cljs.spec.alpha :as s] - [cuerdas.core :as str] - [lentes.core :as l] - [rumext.core :as mx] [rumext.alpha :as mf] [uxbox.builtins.icons :as i] [uxbox.config :as cfg] [uxbox.main.data.auth :as da] [uxbox.main.store :as st] [uxbox.main.ui.messages :refer [messages-widget]] - [uxbox.main.ui.navigation :as nav] [uxbox.util.dom :as dom] [uxbox.util.forms :as fm] - [uxbox.util.i18n :refer (tr)] + [uxbox.util.i18n :refer [tr]] [uxbox.util.router :as rt])) -(def form-data (fm/focus-data :login st/state)) -(def form-errors (fm/focus-errors :login st/state)) - -(def assoc-value (partial fm/assoc-value :login)) -(def assoc-errors (partial fm/assoc-errors :login)) -(def clear-form (partial fm/clear-form :login)) - -(s/def ::username ::fm/non-empty-string) -(s/def ::password ::fm/non-empty-string) - -(s/def ::login-form - (s/keys :req-un [::username ::password])) - -(defn- on-change - [event field] - (let [value (dom/event->value event)] - (st/emit! (assoc-value field value)))) +(def login-form-spec + {:username [fm/required fm/string fm/non-empty-string] + :password [fm/required fm/string fm/non-empty-string]}) (defn- on-submit - [event data] + [event form] (dom/prevent-default event) - (st/emit! (da/login {:username (:username data) - :password (:password data)}))) + (let [{:keys [username password]} (:clean-data form)] + (st/emit! (da/login {:username username + :password password})))) (mf/defc demo-warning [_] @@ -56,33 +38,37 @@ [:strong "DO NOT USE"] " for real work, " [:br] " the projects will be periodicaly wiped."]]) + (mf/defc login-form [] - (let [data (mf/deref form-data) - valid? (fm/valid? ::login-form data)] - [:form {:on-submit #(on-submit % data)} + (let [{:keys [data] :as form} (fm/use-form {:initial {} + :spec login-form-spec})] + [:form {:on-submit #(on-submit % form)} [:div.login-content (when cfg/isdemo [:& demo-warning]) + [:input.input-text - {:name "email" + {:name "username" :tab-index "2" :value (:username data "") - :on-change #(on-change % :username) + :on-blur (fm/on-input-blur form) + :on-change (fm/on-input-change form) :placeholder (tr "auth.email-or-username") :type "text"}] [:input.input-text {:name "password" :tab-index "3" :value (:password data "") - :on-change #(on-change % :password) + :on-blur (fm/on-input-blur form) + :on-change (fm/on-input-change form) :placeholder (tr "auth.password") :type "password"}] [:input.btn-primary {:name "login" :tab-index "4" - :class (when-not valid? "btn-disabled") - :disabled (not valid?) + :class (when (:errors form) "btn-disabled") + :disabled (boolean (:errors form)) :value (tr "auth.signin") :type "submit"}] [:div.login-links @@ -93,12 +79,8 @@ :tab-index "6"} (tr "auth.no-account")]]]])) - -;; {:mixins [mx/static (fm/clear-mixin st/store :login)]} - (mf/defc login-page [] - (mf/use-effect (constantly #(st/emit! (fm/clear-form :login)))) [:div.login [:div.login-body (messages-widget) diff --git a/frontend/src/uxbox/main/ui/dashboard/projects.cljs b/frontend/src/uxbox/main/ui/dashboard/projects.cljs index af9aaf1b1..0b09c3424 100644 --- a/frontend/src/uxbox/main/ui/dashboard/projects.cljs +++ b/frontend/src/uxbox/main/ui/dashboard/projects.cljs @@ -17,6 +17,7 @@ [uxbox.main.ui.modal :as modal] [uxbox.main.ui.keyboard :as kbd] [uxbox.main.ui.confirm :refer [confirm-dialog]] + [uxbox.main.ui.dashboard.projects-forms :refer [create-project-dialog]] [uxbox.util.data :refer [read-string]] [uxbox.util.dom :as dom] [uxbox.util.i18n :as t :refer [tr]] @@ -179,6 +180,7 @@ (sort-projects-by order)) on-click #(do (dom/prevent-default %) + (modal/show! create-project-dialog {}) #_(udl/open! :create-project))] [:section.dashboard-grid [:h2 (tr "ds.project-title")] diff --git a/frontend/src/uxbox/main/ui/dashboard/projects_createform.cljs b/frontend/src/uxbox/main/ui/dashboard/projects_createform.cljs deleted file mode 100644 index 483dec174..000000000 --- a/frontend/src/uxbox/main/ui/dashboard/projects_createform.cljs +++ /dev/null @@ -1,151 +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-2017 Andrey Antukh -;; Copyright (c) 2015-2017 Juan de la Cruz - -(ns uxbox.main.ui.dashboard.projects-createform - (:require - [cljs.spec.alpha :as s] - [cuerdas.core :as str] - [lentes.core :as l] - [rumext.core :as mx] - [rumext.alpha :as mf] - [uxbox.builtins.icons :as i] - [uxbox.main.constants :as c] - [uxbox.main.data.lightbox :as udl] - [uxbox.main.data.projects :as udp] - [uxbox.main.store :as st] - [uxbox.main.ui.lightbox :as lbx] - [uxbox.util.data :refer [read-string parse-int]] - [uxbox.util.dom :as dom] - [uxbox.util.forms :as fm] - [uxbox.util.i18n :as t :refer [tr]] - [uxbox.util.router :as r] - [uxbox.util.time :as dt])) - -(def form-data (fm/focus-data :create-project st/state)) -(def form-errors (fm/focus-errors :create-project st/state)) -(def assoc-value (partial fm/assoc-value :create-project)) -(def clear-form (partial fm/clear-form :create-project)) - -(s/def ::name ::fm/non-empty-string) -(s/def ::layout ::fm/non-empty-string) -(s/def ::width number?) -(s/def ::height number?) - -(s/def ::project-form - (s/keys :req-un [::name - ::width - ::height - ::layout])) - -;; --- Create Project Form - -(mx/defc layout-input - [{:keys [::layout-id] :as data}] - (let [layout (get c/page-layouts layout-id)] - [:div - [:input {:type "radio" - :key layout-id - :id layout-id - :name "project-layout" - :value (:name layout) - :checked (= layout-id (:layout data)) - :on-change #(st/emit! (assoc-value :layout layout-id) - (assoc-value :width (:width layout)) - (assoc-value :height (:height layout)))}] - [:label {:value (:name layout) - :for layout-id} - (:name layout)]])) - -(mx/defc layout-selector - [props] - [:div.input-radio.radio-primary - (layout-input (assoc props ::layout-id "mobile")) - (layout-input (assoc props ::layout-id "tablet")) - (layout-input (assoc props ::layout-id "notebook")) - (layout-input (assoc props ::layout-id "desktop"))]) - -(mf/def create-project-form - :mixins #{mf/reactive} - :render - (fn [own props] - (let [data (merge c/project-defaults (mf/react form-data)) - valid? (fm/valid? ::project-form data)] - (letfn [(on-submit [event] - (dom/prevent-default event) - (when valid? - (st/emit! (udp/create-project data) - (udl/close-lightbox)))) - - (update-size [field e] - (let [value (dom/event->value e) - value (parse-int value)] - (st/emit! (assoc-value field value)))) - - (update-name [e] - (let [value (dom/event->value e)] - (st/emit! (assoc-value :name value)))) - (swap-size [] - (st/emit! (assoc-value :width (:height data)) - (assoc-value :height (:width data))))] - [:form {:on-submit on-submit} - [:input#project-name.input-text - {:placeholder "New project name" - :type "text" - :value (:name data) - :auto-focus true - :on-change update-name}] - [:div.project-size - [:div.input-element.pixels - [:span "Width"] - [:input#project-witdh.input-text - {:placeholder "Width" - :type "number" - :min 0 ;;TODO check this value - :max 666666 ;;TODO check this value - :value (str (:width data)) - :on-change (partial update-size :width)}]] - [:a.toggle-layout {:on-click swap-size} i/toggle] - [:div.input-element.pixels - [:span "Height"] - [:input#project-height.input-text - {:placeholder "Height" - :type "number" - :min 0 ;;TODO check this value - :max 666666 ;;TODO check this value - :value (str (:height data)) - :on-change (partial update-size :height)}]]] - - ;; Layout selector - (layout-selector data) - - ;; Submit - [:input#project-btn.btn-primary - {:value "Go go go!" - :class (when-not valid? "btn-disabled") - :disabled (not valid?) - :type "submit"}]])))) - -;; --- Create Project Lightbox - -(mf/def create-project-lightbox - :will-unmount - (fn [own] - (st/emit! (fm/clear-form :create-project)) - own) - - :render - (fn [own] - [:div.lightbox-body - [:h3 "New project"] - (create-project-form) - [:a.close {:on-click #(st/emit! (udl/close-lightbox))} - i/close]])) - -(defmethod lbx/render-lightbox :create-project - [_] - (create-project-lightbox)) - diff --git a/frontend/src/uxbox/main/ui/dashboard/projects_forms.cljs b/frontend/src/uxbox/main/ui/dashboard/projects_forms.cljs new file mode 100644 index 000000000..67e0d1b77 --- /dev/null +++ b/frontend/src/uxbox/main/ui/dashboard/projects_forms.cljs @@ -0,0 +1,97 @@ +;; 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-2019 Andrey Antukh +;; Copyright (c) 2015-2019 Juan de la Cruz + +(ns uxbox.main.ui.dashboard.projects-forms + (:require + [cljs.spec.alpha :as s] + [rumext.alpha :as mf] + [uxbox.builtins.icons :as i] + [uxbox.main.data.projects :as udp] + [uxbox.main.store :as st] + [uxbox.main.ui.modal :as modal] + [uxbox.util.dom :as dom] + [uxbox.util.forms :as fm] + [uxbox.util.i18n :as t :refer [tr]])) + +(def project-form-spec + {:name [fm/required fm/string fm/non-empty-string] + :width [fm/required fm/number-str] + :height [fm/required fm/number-str]}) + +(def defaults + {:name "" + :width "1366" + :height "768"}) + +;; --- Create Project Form + +(defn- on-submit + [event form] + (dom/prevent-default event) + (let [data (:clean-data form)] + (st/emit! (udp/create-project data)) + (modal/hide!))) + +(defn- swap-size + [event {:keys [data] :as form}] + (swap! data assoc + :width (:height data) + :height (:width data))) + +(mf/defc create-project-form + [props] + (let [{:keys [data errors] :as form} (fm/use-form {:initial defaults :spec project-form-spec})] + [:form {:on-submit #(on-submit % form)} + [:input.input-text + {:placeholder "New project name" + :type "text" + :name "name" + :value (:name data) + :on-blur (fm/on-input-blur form) + :on-change (fm/on-input-change form) + :auto-focus true}] + [:div.project-size + [:div.input-element.pixels + [:span "Width"] + [:input#project-witdh.input-text + {:placeholder "Width" + :name "width" + :type "number" + :min 0 + :max 5000 + :on-blur (fm/on-input-blur form) + :on-change (fm/on-input-change form) + :value (:width data)}]] + [:a.toggle-layout {:on-click #(swap-size % form)} i/toggle] + [:div.input-element.pixels + [:span "Height"] + [:input#project-height.input-text + {:placeholder "Height" + :type "number" + :name "height" + :min 0 + :max 5000 + :on-blur (fm/on-input-blur form) + :on-change (fm/on-input-change form) + :value (:height data)}]]] + + ;; Submit + [:input#project-btn.btn-primary + {:value "Go go go!" + :class (when-not (:valid form) "btn-disabled") + :disabled (not (:valid form)) + :type "submit"}]])) + +;; --- Create Project Lightbox + +(mf/defc create-project-dialog + [props] + [:div.lightbox-body + [:h3 "New project"] + [:& create-project-form] + [:a.close {:on-click modal/hide!} i/close]]) + diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar/sitemap.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar/sitemap.cljs index ae929a78d..c8aba1205 100644 --- a/frontend/src/uxbox/main/ui/workspace/sidebar/sitemap.cljs +++ b/frontend/src/uxbox/main/ui/workspace/sidebar/sitemap.cljs @@ -7,8 +7,8 @@ (ns uxbox.main.ui.workspace.sidebar.sitemap (:require - ;; [uxbox.main.data.lightbox :as udl] - ;; [uxbox.main.ui.workspace.sidebar.sitemap-pageform] + [potok.core :as ptk] + [beicon.core :as rx] [cuerdas.core :as str] [lentes.core :as l] [rumext.alpha :as mf] @@ -19,6 +19,7 @@ [uxbox.main.store :as st] [uxbox.main.ui.confirm :refer [confirm-dialog]] [uxbox.main.ui.modal :as modal] + [uxbox.main.ui.workspace.sidebar.sitemap-forms :refer [page-form-dialog]] [uxbox.main.ui.workspace.sortable :refer [use-sortable]] [uxbox.util.data :refer [classnames]] [uxbox.util.dom :as dom] @@ -30,17 +31,15 @@ (mf/defc page-item [{:keys [page index deletable? selected?] :as props}] (letfn [(on-edit [event] - #_(udl/open! :page-form {:page page})) + (modal/show! page-form-dialog {:page page})) (delete [] - (let [next #(st/emit! (dp/go-to (:project page)))] - (st/emit! (udp/delete-page (:id page) next)))) - + (st/emit! (dw/delete-page (:id page)))) (on-delete [event] (dom/prevent-default event) (dom/stop-propagation event) (modal/show! confirm-dialog {:on-accept delete})) (on-drop [item monitor] - (st/emit! (udp/reorder-pages (:project page)))) + (st/emit! (udp/rehash-pages (:project page)))) (on-hover [item monitor] (st/emit! (udp/move-page {:project-id (:project-id item) :page-id (:page-id item) @@ -99,7 +98,7 @@ :fn #(-> (l/in [:projects project-id]) (l/derive st/state))}) project (mf/deref project-iref) - ;; create #(udl/open! :page-form {:page {:project project-id}}) + create #(modal/show! page-form-dialog {:page {:project project-id}}) close #(st/emit! (dw/toggle-flag :sitemap))] [:div.sitemap.tool-window [:div.tool-window-bar @@ -109,6 +108,6 @@ [:div.tool-window-content [:div.project-title [:span (:name project)] - [:div.add-page #_{:on-click create} i/close]] + [:div.add-page {:on-click create} i/close]] [:& pages-list {:project project :current-page-id current-page-id}]]])) diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar/sitemap_forms.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar/sitemap_forms.cljs new file mode 100644 index 000000000..d01c3b0b3 --- /dev/null +++ b/frontend/src/uxbox/main/ui/workspace/sidebar/sitemap_forms.cljs @@ -0,0 +1,103 @@ +;; 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 +;; Copyright (c) 2015-2016 Juan de la Cruz + +(ns uxbox.main.ui.workspace.sidebar.sitemap-forms + (:require + [rumext.alpha :as mf] + [uxbox.builtins.icons :as i] + [uxbox.main.constants :as c] + [uxbox.main.data.pages :as udp] + [uxbox.main.store :as st] + [uxbox.main.ui.modal :as modal] + [uxbox.util.dom :as dom] + [uxbox.util.forms :as fm] + [uxbox.util.i18n :refer [tr]])) + +(def page-form-spec + {:id [fm/uuid] + :project [fm/uuid] + :name [fm/required fm/string fm/non-empty-string] + :width [fm/required fm/number-str] + :height [fm/required fm/number-str]}) + +(def defaults + {:name "" + :width "1366" + :height "768"}) + +(defn- on-submit + [event form] + (dom/prevent-default event) + (modal/hide!) + (let [data (:clean-data form)] + (if (nil? (:id data)) + (st/emit! (udp/create-page data)) + (st/emit! (udp/persist-page-update-form data))))) + +(defn- swap-size + [event {:keys [data] :as form}] + (swap! data assoc + :width (:height data) + :height (:width data))) + +(defn- initial-data + [page] + (merge {:name "" :width "1366" :height "768"} + (select-keys page [:name :id :project]) + (select-keys (:metadata page) [:width :height]))) + +(mf/defc page-form + [{:keys [page] :as props}] + (let [{:keys [data errors] :as form} (fm/use-form {:initial #(initial-data page) + :spec page-form-spec})] + [:form {:on-submit #(on-submit % form)} + [:input.input-text + {:placeholder "Page name" + :type "text" + :name "name" + :on-blur (fm/on-input-blur form) + :on-change (fm/on-input-change form) + :value (:name data) + :auto-focus true}] + [:div.project-size + [:div.input-element.pixels + [:span "Width"] + [:input#project-witdh.input-text + {:placeholder "Width" + :name "width" + :type "number" + :min 0 + :max 5000 + :on-blur (fm/on-input-blur form) + :on-change (fm/on-input-change form) + :value (:width data)}]] + [:a.toggle-layout {:on-click #(swap-size % form)} i/toggle] + [:div.input-element.pixels + [:span "Height"] + [:input#project-height.input-text + {:placeholder "Height" + :name "height" + :type "number" + :min 0 + :max 5000 + :on-blur (fm/on-input-blur form) + :on-change (fm/on-input-change form) + :value (:height data)}]]] + [:input.btn-primary + {:value "Go go go!" + :type "submit" + :disabled (not (:valid form))}]])) + +(mf/defc page-form-dialog + [{:keys [page] :as props}] + [:div.lightbox-body + (if (nil? (:id page)) + [:h3 "New page"] + [:h3 "Edit page"]) + [:& page-form {:page page}] + [:a.close {:on-click modal/hide!} i/close]]) + diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar/sitemap_pageform.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar/sitemap_pageform.cljs deleted file mode 100644 index 362f02f78..000000000 --- a/frontend/src/uxbox/main/ui/workspace/sidebar/sitemap_pageform.cljs +++ /dev/null @@ -1,145 +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 -;; Copyright (c) 2015-2016 Juan de la Cruz - -(ns uxbox.main.ui.workspace.sidebar.sitemap-pageform - (:require [cljs.spec.alpha :as s :include-macros true] - [lentes.core :as l] - [uxbox.builtins.icons :as i] - [uxbox.main.store :as st] - [uxbox.main.constants :as c] - [uxbox.main.data.pages :as udp] - [uxbox.main.data.lightbox :as udl] - [uxbox.main.ui.lightbox :as lbx] - [uxbox.util.data :refer [parse-int]] - [uxbox.util.dom :as dom] - [uxbox.util.forms :as fm] - [uxbox.util.i18n :refer [tr]] - [uxbox.util.router :as r] - [rumext.core :as mx :include-macros true])) - - -(def form-data (fm/focus-data :workspace-page-form st/state)) -(def form-errors (fm/focus-errors :workspace-page-form st/state)) - -(def assoc-value (partial fm/assoc-value :workspace-page-form)) -(def assoc-error (partial fm/assoc-error :workspace-page-form)) -(def clear-form (partial fm/clear-form :workspace-page-form)) - -;; --- Lightbox - -(s/def ::name ::fm/non-empty-string) -(s/def ::layout ::fm/non-empty-string) -(s/def ::width number?) -(s/def ::height number?) - -(s/def ::page-form - (s/keys :req-un [::name - ::width - ::height - ::layout])) - -(mx/defc layout-input - [data id] - (let [{:keys [id name width height]} (get c/page-layouts id)] - (letfn [(on-change [event] - (st/emit! (assoc-value :layout id) - (assoc-value :width width) - (assoc-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 c/page-defaults - (select-keys page [:name :id :project]) - (select-keys metadata [:width :height :layout]) - (mx/react form-data)) - valid? (fm/valid? ::page-form data)] - (letfn [(update-size [field e] - (let [value (dom/event->value e) - value (parse-int value)] - (st/emit! (assoc-value field value)))) - (update-name [e] - (let [value (dom/event->value e)] - (st/emit! (assoc-value :name value)))) - (toggle-sizes [] - (let [{:keys [width height]} data] - (st/emit! (assoc-value :width width) - (assoc-value :height height)))) - (on-cancel [e] - (dom/prevent-default e) - (udl/close!)) - (on-save [e] - (dom/prevent-default e) - (udl/close!) - (if (nil? id) - (st/emit! (udp/create-page data)) - (st/emit! (udp/persist-page-update-form 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 - [:span "Width"] - [: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 - [:span "Height"] - [: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")] - - [:input#project-btn.btn-primary - {:value "Go go go!" - :disabled (not valid?) - :on-click on-save - :type "button"}]]))) - -(mx/defc page-form-lightbox - {:mixins [mx/static (fm/clear-mixin st/store :workspace-page-form)]} - [{: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)) diff --git a/frontend/src/uxbox/util/forms.cljs b/frontend/src/uxbox/util/forms.cljs index b1a81a61a..5d2427c66 100644 --- a/frontend/src/uxbox/util/forms.cljs +++ b/frontend/src/uxbox/util/forms.cljs @@ -5,15 +5,97 @@ ;; Copyright (c) 2015-2017 Andrey Antukh (ns uxbox.util.forms + (:refer-clojure :exclude [uuid]) (:require [beicon.core :as rx] [cljs.spec.alpha :as s :include-macros true] [cuerdas.core :as str] [lentes.core :as l] [potok.core :as ptk] - [rumext.core :as mx :include-macros true] + [rumext.alpha :as mf] + [rumext.core :as mx] + [struct.core :as stt] + [uxbox.util.dom :as dom] [uxbox.util.i18n :refer [tr]])) +;; --- Main Api + +(defn validate + [data spec] + (stt/validate data spec)) + +(defn valid? + [data spec] + (stt/valid? data spec)) + +;; --- 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 use-form + [{:keys [initial spec] :as opts}] + (let [[data update-data] (mf/useState initial) + [errors update-errors] (mf/useState nil) + [touched update-touched] (mf/useState {}) + [errors' clean-data] (validate data spec) + + data (impl-mutator data update-data) + errors (-> (merge {} errors' errors) + (impl-mutator update-errors)) + touched (impl-mutator touched update-touched)] + {:clean-data clean-data + :touched touched + :data data + :errors errors + :valid (not (seq errors))})) + +(defn on-input-change + [{:keys [data] :as form}] + (fn [event] + (let [target (dom/get-target event) + field (keyword (.-name target)) + value (dom/get-value target)] + (swap! data assoc field value)))) + +(defn on-input-blur + [{:keys [touched] :as form}] + (fn [event] + (let [target (dom/get-target event) + field (keyword (.-name target))] + (when-not (get touched field) + (swap! touched assoc field true))))) + +;; --- Additional Validators + +(def non-empty-string + {:message "errors.empty-string" + :optional true + :validate #(not (str/empty? %))}) + +(def string (assoc stt/string :message "errors.should-be-string")) +(def number (assoc stt/number :message "errors.should-be-number")) +(def number-str (assoc stt/number-str :message "errors.should-be-number")) +(def integer (assoc stt/integer :message "errors.should-be-integer")) +(def integer-str (assoc stt/integer-str :message "errors.should-be-integer")) +(def required (assoc stt/required :message "errors.required")) +(def email (assoc stt/email :message "errors.should-be-valid-email")) +(def uuid (assoc stt/uuid :message "errors.should-be-uuid")) +(def uuid-str (assoc stt/uuid-str :message "errors.should-be-valid-uuid")) + +;; DEPRECATED + ;; --- Form Validation Api (defn- interpret-problem @@ -30,15 +112,16 @@ :else acc)) -(defn validate - [spec data] - (when-not (s/valid? spec data) - (let [report (s/explain-data spec data)] - (reduce interpret-problem {} (::s/problems report))))) +;; (defn validate +;; [spec data] +;; (when-not (s/valid? spec data) +;; (let [report (s/explain-data spec data)] +;; (reduce interpret-problem {} (::s/problems report))))) + +;; (defn valid? +;; [spec data] +;; (s/valid? spec data)) -(defn valid? - [spec data] - (s/valid? spec data)) ;; --- Form Specs and Conformers