From faf7877d0079a013f8d29670e7a509a1c461d243 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 9 Sep 2019 23:21:55 +0200 Subject: [PATCH] :construction: Use cljs.spec everywhere. --- frontend/deps.edn | 1 - frontend/src/uxbox/main/data/dashboard.cljs | 1 - frontend/src/uxbox/main/data/workspace.cljs | 1 - frontend/src/uxbox/main/ui/auth/login.cljs | 2 +- frontend/src/uxbox/main/ui/auth/recovery.cljs | 2 +- frontend/src/uxbox/main/ui/auth/register.cljs | 18 +- .../main/ui/dashboard/projects_forms.cljs | 12 +- .../src/uxbox/main/ui/settings/password.cljs | 2 +- .../src/uxbox/main/ui/settings/profile.cljs | 2 +- .../ui/workspace/sidebar/sitemap_forms.cljs | 21 +- frontend/src/uxbox/util/forms.cljs | 245 +++--------------- frontend/src/uxbox/util/forms2.cljs | 145 ----------- frontend/src/uxbox/util/messages.cljs | 1 - frontend/src/uxbox/util/spec.cljs | 30 ++- frontend/src/uxbox/view/data/viewer.cljs | 1 - 15 files changed, 93 insertions(+), 391 deletions(-) delete mode 100644 frontend/src/uxbox/util/forms2.cljs diff --git a/frontend/deps.edn b/frontend/deps.edn index ab9b1dfead..29f93a12bc 100644 --- a/frontend/deps.edn +++ b/frontend/deps.edn @@ -7,7 +7,6 @@ environ/environ {:mvn/version "1.1.0"} metosin/reitit-core {:mvn/version "0.3.9"} - funcool/struct {:mvn/version "2.0.0-SNAPSHOT"} funcool/beicon {:mvn/version "5.1.0"} funcool/cuerdas {:mvn/version "2.2.0"} funcool/lentes {:mvn/version "1.3.0-SNAPSHOT"} diff --git a/frontend/src/uxbox/main/data/dashboard.cljs b/frontend/src/uxbox/main/data/dashboard.cljs index a56aae4700..ccd0ee4d33 100644 --- a/frontend/src/uxbox/main/data/dashboard.cljs +++ b/frontend/src/uxbox/main/data/dashboard.cljs @@ -10,7 +10,6 @@ [potok.core :as ptk] [uxbox.util.router :as r] [uxbox.main.store :as st] - [uxbox.util.forms :as sc] [uxbox.main.repo :as rp] [uxbox.main.data.projects :as dp] [uxbox.main.data.colors :as dc] diff --git a/frontend/src/uxbox/main/data/workspace.cljs b/frontend/src/uxbox/main/data/workspace.cljs index da4a8be357..306c454ca2 100644 --- a/frontend/src/uxbox/main/data/workspace.cljs +++ b/frontend/src/uxbox/main/data/workspace.cljs @@ -21,7 +21,6 @@ [uxbox.main.store :as st] [uxbox.main.workers :as uwrk] [uxbox.util.data :refer [dissoc-in index-of]] - [uxbox.util.forms :as sc] [uxbox.util.geom.matrix :as gmt] [uxbox.util.geom.point :as gpt] [uxbox.util.math :as mth] diff --git a/frontend/src/uxbox/main/ui/auth/login.cljs b/frontend/src/uxbox/main/ui/auth/login.cljs index 7edd5d571f..a37c7e6ed6 100644 --- a/frontend/src/uxbox/main/ui/auth/login.cljs +++ b/frontend/src/uxbox/main/ui/auth/login.cljs @@ -15,7 +15,7 @@ [uxbox.main.store :as st] [uxbox.main.ui.messages :refer [messages-widget]] [uxbox.util.dom :as dom] - [uxbox.util.forms2 :as fm2] + [uxbox.util.forms :as fm2] [uxbox.util.i18n :refer [tr]] [uxbox.util.router :as rt])) diff --git a/frontend/src/uxbox/main/ui/auth/recovery.cljs b/frontend/src/uxbox/main/ui/auth/recovery.cljs index c444767bb0..daa9b7a099 100644 --- a/frontend/src/uxbox/main/ui/auth/recovery.cljs +++ b/frontend/src/uxbox/main/ui/auth/recovery.cljs @@ -7,7 +7,7 @@ (ns uxbox.main.ui.auth.recovery (:require - [cljs.spec.alpha :as s :include-macros true] + [cljs.spec.alpha :as s] [cuerdas.core :as str] [lentes.core :as l] [rumext.core :as mx :include-macros true] diff --git a/frontend/src/uxbox/main/ui/auth/register.cljs b/frontend/src/uxbox/main/ui/auth/register.cljs index e9fe977861..db3f2d6488 100644 --- a/frontend/src/uxbox/main/ui/auth/register.cljs +++ b/frontend/src/uxbox/main/ui/auth/register.cljs @@ -7,10 +7,10 @@ (ns uxbox.main.ui.auth.register (:require + [cljs.spec.alpha :as s] [cuerdas.core :as str] [lentes.core :as l] [rumext.alpha :as mf] - [struct.alpha :as s] [uxbox.builtins.icons :as i] [uxbox.main.data.auth :as uda] [uxbox.main.store :as st] @@ -21,11 +21,16 @@ [uxbox.util.i18n :refer [tr]] [uxbox.util.router :as rt])) -(s/defs ::register-form - (s/dict :username (s/&& ::s/string ::fm/not-empty-string) - :fullname (s/&& ::s/string ::fm/not-empty-string) - :password (s/&& ::s/string ::fm/not-empty-string) - :email ::s/email)) +(s/def ::username ::fm/not-empty-string) +(s/def ::fullname ::fm/not-empty-string) +(s/def ::password ::fm/not-empty-string) +(s/def ::email ::fm/email) + +(s/def ::register-form + (s/keys :req-un [::username + ::password + ::fullname + ::email])) (defn- on-error [error form] @@ -112,7 +117,6 @@ :type #{::api} :field :email}] - [:input.btn-primary {:type "submit" :tab-index "5" diff --git a/frontend/src/uxbox/main/ui/dashboard/projects_forms.cljs b/frontend/src/uxbox/main/ui/dashboard/projects_forms.cljs index 119161ccfb..d6695623b6 100644 --- a/frontend/src/uxbox/main/ui/dashboard/projects_forms.cljs +++ b/frontend/src/uxbox/main/ui/dashboard/projects_forms.cljs @@ -7,7 +7,7 @@ (ns uxbox.main.ui.dashboard.projects-forms (:require - [struct.alpha :as s] + [cljs.spec.alpha :as s] [rumext.alpha :as mf] [uxbox.builtins.icons :as i] [uxbox.main.data.projects :as udp] @@ -17,10 +17,12 @@ [uxbox.util.forms :as fm] [uxbox.util.i18n :as t :refer [tr]])) -(s/defs ::project-form - (s/dict :name (s/&& ::s/string ::fm/not-empty-string) - :width ::s/number-str - :height ::s/number-str)) +(s/def ::name ::fm/not-empty-string) +(s/def ::width ::fm/number-str) +(s/def ::height ::fm/number-str) + +(s/def ::project-form + (s/keys :req-un [::name ::width ::height])) (def defaults {:name "" diff --git a/frontend/src/uxbox/main/ui/settings/password.cljs b/frontend/src/uxbox/main/ui/settings/password.cljs index fa559aabb2..fa286f9c1d 100644 --- a/frontend/src/uxbox/main/ui/settings/password.cljs +++ b/frontend/src/uxbox/main/ui/settings/password.cljs @@ -13,7 +13,7 @@ [uxbox.main.data.users :as udu] [uxbox.main.store :as st] [uxbox.util.dom :as dom] - [uxbox.util.forms2 :as fm] + [uxbox.util.forms :as fm] [uxbox.util.i18n :refer [tr]] [uxbox.util.messages :as um])) diff --git a/frontend/src/uxbox/main/ui/settings/profile.cljs b/frontend/src/uxbox/main/ui/settings/profile.cljs index 584935ff35..82ec47fe54 100644 --- a/frontend/src/uxbox/main/ui/settings/profile.cljs +++ b/frontend/src/uxbox/main/ui/settings/profile.cljs @@ -16,7 +16,7 @@ [uxbox.main.store :as st] [uxbox.util.data :refer [read-string]] [uxbox.util.dom :as dom] - [uxbox.util.forms2 :as fm] + [uxbox.util.forms :as fm] [uxbox.util.i18n :as i18n :refer [tr]] [uxbox.util.interop :refer [iterable->seq]] [uxbox.util.messages :as um])) diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar/sitemap_forms.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar/sitemap_forms.cljs index 8e3c410c4b..4ec95b7416 100644 --- a/frontend/src/uxbox/main/ui/workspace/sidebar/sitemap_forms.cljs +++ b/frontend/src/uxbox/main/ui/workspace/sidebar/sitemap_forms.cljs @@ -8,22 +8,29 @@ (ns uxbox.main.ui.workspace.sidebar.sitemap-forms (:require [rumext.alpha :as mf] - [struct.alpha :as s] + [cljs.spec.alpha :as s] [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.spec :as us] [uxbox.util.forms :as fm] [uxbox.util.i18n :refer [tr]])) -(s/defs ::page-form - (s/dict :id (s/opt ::s/uuid) - :project ::s/uuid - :name (s/&& ::s/string ::fm/not-empty-string) - :width ::s/number-str - :height ::s/number-str)) +(s/def ::id ::us/uuid) +(s/def ::project ::us/uuid) +(s/def ::name ::us/not-empty-string) +(s/def ::width ::us/number-str) +(s/def ::height ::us/number-str) + +(s/def ::page-form + (s/keys :req-un [::id + ::project + ::name + ::width + ::height])) (def defaults {:name "" diff --git a/frontend/src/uxbox/util/forms.cljs b/frontend/src/uxbox/util/forms.cljs index 70e5db5435..02f91253f4 100644 --- a/frontend/src/uxbox/util/forms.cljs +++ b/frontend/src/uxbox/util/forms.cljs @@ -13,9 +13,8 @@ [lentes.core :as l] [potok.core :as ptk] [rumext.alpha :as mf] - [rumext.core :as mx] - [struct.alpha :as st] [uxbox.util.dom :as dom] + [uxbox.util.spec :as us] [uxbox.util.i18n :refer [tr]])) ;; --- Handlers Helpers @@ -36,43 +35,41 @@ (defn- translate-error-type [name] - (case name - ::st/string "errors.form.string" - ::st/number "errors.form.number" - ::st/number-str "errors.form.number" - ::st/integer "errors.form.integer" - ::st/integer-str "errors.form.integer" - ::st/required "errors.form.required" - ::st/email "errors.form.email" - ;; ::st/identical-to "errors.form.does-not-match" - "errors.undefined-error")) + "errors.undefined-error") -(defn- process-errors - [errors] - (reduce (fn [acc {:keys [path name] :as error}] - (let [message (translate-error-type name)] - (assoc-in acc path - (-> (assoc error :message message) - (dissoc :path))))) - {} errors)) +(defn- interpret-problem + [acc {:keys [path pred val via in] :as problem}] + ;; (prn "interpret-problem" problem) + (cond + (and (empty? path) + (list? pred) + (= (first (last pred)) 'cljs.core/contains?)) + (let [path (conj path (last (last pred)))] + (assoc-in acc path {:name ::missing :type :builtin})) + + (and (not (empty? path)) + (not (empty? via))) + (assoc-in acc path {:name (last via) :type :builtin}) + + :else acc)) (defn use-form [spec initial] (let [[state update-state] (mf/useState {:data (if (fn? initial) (initial) initial) :errors {} :touched {}}) - cdata (st/conform spec (:data state)) - errors' (when (= ::st/invalid cdata) - (st/explain spec (:data state))) + clean-data (s/conform spec (:data state)) + problems (when (= ::s/invalid clean-data) + (::s/problems (s/explain-data spec (:data state)))) - errors (merge (process-errors errors') + + errors (merge (reduce interpret-problem {} problems) (:errors state))] - (-> (assoc state :errors errors - :clean-data (when (not= cdata ::st/invalid) cdata) + :clean-data (when (not= clean-data ::s/invalid) clean-data) :valid (and (empty? errors) - (not= cdata ::st/invalid))) + (not= clean-data ::s/invalid))) (impl-mutator update-state)))) (defn on-input-change @@ -103,9 +100,10 @@ (when (and touched? error (cond (nil? type) true - (ifn? type) (type (:type error)) (keyword? type) (= (:type error) type) + (ifn? type) (type (:type error)) :else false)) + (prn "field-error" error) [:ul.form-errors [:li {:key code} (tr message)]]))) @@ -115,191 +113,10 @@ (get-in form [:touched field])) "invalid")) -;; --- Additional Validators - -(st/defs ::not-empty-string #(not (empty? %))) - -;; (def string (assoc st/string :message "errors.should-be-string")) -;; (def number (assoc st/number :message "errors.should-be-number")) -;; (def number-str (assoc st/number-str :message "errors.should-be-number")) -;; (def integer (assoc st/integer :message "errors.should-be-integer")) -;; (def integer-str (assoc st/integer-str :message "errors.should-be-integer")) -;; (def required (assoc st/required :message "errors.required")) -;; (def email (assoc st/email :message "errors.should-be-valid-email")) -;; (def uuid (assoc st/uuid :message "errors.should-be-uuid")) -;; (def uuid-str (assoc st/uuid-str :message "errors.should-be-valid-uuid")) - -;; DEPRECATED - -;; --- Form Validation Api - ;; --- Form Specs and Conformers -(def ^:private email-re - #"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$") - -(def ^:private number-re - #"^[-+]?[0-9]*\.?[0-9]+$") - -(def ^:private color-re - #"^#[0-9A-Fa-f]{6}$") - -(s/def ::email - (s/and string? #(boolean (re-matches email-re %)))) - -(s/def ::non-empty-string - (s/and string? #(not (str/empty? %)))) - -(s/def ::not-empty #(not (str/empty? %))) - - -(defn- parse-number - [v] - (cond - (re-matches number-re v) (js/parseFloat v) - (number? v) v - :else ::s/invalid)) - -(s/def ::string-number - (s/conformer parse-number str)) - -(s/def ::color - (s/and string? #(boolean (re-matches color-re %)))) - - - -;; --- Form State Events - -;; --- Assoc Error - -(defrecord AssocError [type field error] - ptk/UpdateEvent - (update [_ state] - (assoc-in state [:errors type field] error))) - -(defn assoc-error - ([type field] - (assoc-error type field nil)) - ([type field error] - {:pre [(keyword? type) - (keyword? field) - (any? error)]} - (AssocError. type field error))) - -;; --- Assoc Errors - -(defrecord AssocErrors [type errors] - ptk/UpdateEvent - (update [_ state] - (assoc-in state [:errors type] errors))) - -(defn assoc-errors - ([type] - (assoc-errors type nil)) - ([type errors] - {:pre [(keyword? type) - (or (map? errors) - (nil? errors))]} - (AssocErrors. type errors))) - -;; --- Assoc Value - -(declare clear-error) - -(defrecord AssocValue [type field value] - ptk/UpdateEvent - (update [_ state] - (let [form-path (into [:forms type] (if (coll? field) field [field]))] - (assoc-in state form-path value))) - - ptk/WatchEvent - (watch [_ state stream] - (rx/of (clear-error type field)))) - -(defn assoc-value - [type field value] - {:pre [(keyword? type) - (keyword? field) - (any? value)]} - (AssocValue. type field value)) - -;; --- Clear Values - -(defrecord ClearValues [type] - ptk/UpdateEvent - (update [_ state] - (assoc-in state [:forms type] nil))) - -(defn clear-values - [type] - {:pre [(keyword? type)]} - (ClearValues. type)) - -;; --- Clear Error - -(deftype ClearError [type field] - ptk/UpdateEvent - (update [_ state] - (let [errors (get-in state [:errors type])] - (if (map? errors) - (assoc-in state [:errors type] (dissoc errors field)) - (update state :errors dissoc type))))) - -(defn clear-error - [type field] - {:pre [(keyword? type) - (keyword? field)]} - (ClearError. type field)) - -;; --- Clear Errors - -(defrecord ClearErrors [type] - ptk/UpdateEvent - (update [_ state] - (assoc-in state [:errors type] nil))) - -(defn clear-errors - [type] - {:pre [(keyword? type)]} - (ClearErrors. type)) - -;; --- Clear Form - -(deftype ClearForm [type] - ptk/WatchEvent - (watch [_ state stream] - (rx/of (clear-values type) - (clear-errors type)))) - -(defn clear-form - [type] - {:pre [(keyword? type)]} - (ClearForm. 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 clear-mixin - [store type] - {:will-unmount (fn [own] - (ptk/emit! store (clear-form type)) - own)}) +;; TODO: migrate to uxbox.util.spec +(s/def ::email ::us/email) +(s/def ::not-empty-string ::us/not-empty-string) +(s/def ::color ::us/color) +(s/def ::number-str ::us/number-str) diff --git a/frontend/src/uxbox/util/forms2.cljs b/frontend/src/uxbox/util/forms2.cljs deleted file mode 100644 index 8c3b8735ce..0000000000 --- a/frontend/src/uxbox/util/forms2.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-2017 Andrey Antukh - -(ns uxbox.util.forms2 - (:refer-clojure :exclude [uuid]) - (:require - [beicon.core :as rx] - [cljs.spec.alpha :as s] - [cuerdas.core :as str] - [lentes.core :as l] - [potok.core :as ptk] - [rumext.alpha :as mf] - [uxbox.util.dom :as dom] - [uxbox.util.i18n :refer [tr]])) - -;; --- Handlers Helpers - -(defn- impl-mutator - [v update-fn] - (specify v - IReset - (-reset! [_ new-value] - (update-fn new-value)) - - ISwap - (-swap! - ([self f] (update-fn f)) - ([self f x] (update-fn #(f % x))) - ([self f x y] (update-fn #(f % x y))) - ([self f x y more] (update-fn #(apply f % x y more)))))) - -(defn- translate-error-type - [name] - "errors.undefined-error") - -(defn- interpret-problem - [acc {:keys [path pred val via in] :as problem}] - ;; (prn "interpret-problem" problem) - (cond - (and (empty? path) - (list? pred) - (= (first (last pred)) 'cljs.core/contains?)) - (let [path (conj path (last (last pred)))] - (assoc-in acc path {:name ::missing :type :builtin})) - - (and (not (empty? path)) - (not (empty? via))) - (assoc-in acc path {:name (last via) :type :builtin}) - - :else acc)) - -(defn use-form - [spec initial] - (let [[state update-state] (mf/useState {:data (if (fn? initial) (initial) initial) - :errors {} - :touched {}}) - clean-data (s/conform spec (:data state)) - problems (when (= ::s/invalid clean-data) - (::s/problems (s/explain-data spec (:data state)))) - - - errors (merge (reduce interpret-problem {} problems) - (:errors state))] - (-> (assoc state - :errors errors - :clean-data (when (not= clean-data ::s/invalid) clean-data) - :valid (and (empty? errors) - (not= clean-data ::s/invalid))) - (impl-mutator update-state)))) - -(defn on-input-change - [{:keys [data] :as form} field] - (fn [event] - (let [target (dom/get-target event) - value (dom/get-value target)] - (swap! form (fn [state] - (-> state - (assoc-in [:data field] value) - (update :errors dissoc field))))))) - -(defn on-input-blur - [{:keys [touched] :as form} field] - (fn [event] - (let [target (dom/get-target event)] - (when-not (get touched field) - (swap! form assoc-in [:touched field] true))))) - -;; --- Helper Components - -(mf/defc field-error - [{:keys [form field type] - :or {only (constantly true)} - :as props}] - (let [touched? (get-in form [:touched field]) - {:keys [message code] :as error} (get-in form [:errors field])] - (when (and touched? error - (cond - (nil? type) true - (keyword? type) (= (:type error) type) - (ifn? type) (type (:type error)) - :else false)) - (prn "field-error" error) - [:ul.form-errors - [:li {:key code} (tr message)]]))) - -(defn error-class - [form field] - (when (and (get-in form [:errors field]) - (get-in form [:touched field])) - "invalid")) - -;; --- Form Validation Api - -;; --- Form Specs and Conformers - -(def ^:private email-re - #"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$") - -(def ^:private number-re - #"^[-+]?[0-9]*\.?[0-9]+$") - -(def ^:private color-re - #"^#[0-9A-Fa-f]{6}$") - -(s/def ::email - (s/and string? #(boolean (re-matches email-re %)))) - -(s/def ::not-empty-string - (s/and string? #(not (str/empty? %)))) - -(defn- parse-number - [v] - (cond - (re-matches number-re v) (js/parseFloat v) - (number? v) v - :else ::s/invalid)) - -(s/def ::string-number - (s/conformer parse-number str)) - -(s/def ::color - (s/and string? #(boolean (re-matches color-re %)))) diff --git a/frontend/src/uxbox/util/messages.cljs b/frontend/src/uxbox/util/messages.cljs index f5d50a9878..1847256167 100644 --- a/frontend/src/uxbox/util/messages.cljs +++ b/frontend/src/uxbox/util/messages.cljs @@ -140,7 +140,6 @@ (mf/defc messages-widget [{:keys [message] :as props}] - (prn "messages-widget" props) (case (:type message) :error (mf/element notification-box props) :info (mf/element notification-box props) diff --git a/frontend/src/uxbox/util/spec.cljs b/frontend/src/uxbox/util/spec.cljs index 592cbf11a9..904f0392d6 100644 --- a/frontend/src/uxbox/util/spec.cljs +++ b/frontend/src/uxbox/util/spec.cljs @@ -5,7 +5,9 @@ ;; Copyright (c) 2015-2016 Andrey Antukh (ns uxbox.util.spec - (:require [cljs.spec.alpha :as s])) + (:require [cljs.spec.alpha :as s] + [cuerdas.core :as str])) + ;; --- Constants @@ -15,11 +17,17 @@ (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}$") +(def number-rx + #"^[+-]?([0-9]*\.?[0-9]+|[0-9]+\.?[0-9]*)([eE][+-]?[0-9]+)?$") + +(def ^:private color-re + #"^#[0-9A-Fa-f]{6}$") + ;; --- Predicates (defn email? [v] - (and string? + (and (string? v) (re-matches email-rx v))) (defn color? @@ -31,8 +39,6 @@ [v] (instance? js/File v)) -;; TODO: properly implement - (defn url-str? [v] (string? v)) @@ -49,6 +55,22 @@ (s/def ::keyword keyword?) (s/def ::fn fn?) +(s/def ::not-empty-string + (s/and string? #(not (str/empty? %)))) + + +(defn- conform-number-str + [v] + (cond + (re-matches number-rx v) (js/parseFloat v) + (number? v) v + :else ::s/invalid)) + +(s/def ::number-str + (s/conformer conform-number-str str)) + +(s/def ::color color?) + ;; --- Public Api (defn valid? diff --git a/frontend/src/uxbox/view/data/viewer.cljs b/frontend/src/uxbox/view/data/viewer.cljs index fd8fcddcbc..7ae36890bf 100644 --- a/frontend/src/uxbox/view/data/viewer.cljs +++ b/frontend/src/uxbox/view/data/viewer.cljs @@ -8,7 +8,6 @@ (:require [beicon.core :as rx] [potok.core :as ptk] [uxbox.util.router :as rt] - [uxbox.util.forms :as sc] [uxbox.util.data :refer (parse-int)] [uxbox.main.repo :as rp] [uxbox.main.data.pages :as udpg]