Refactor settings pages and add tha ability to change current locale.

This commit is contained in:
Andrey Antukh 2019-07-21 19:09:37 +02:00
parent 76726b6cd2
commit 14d97511e6
12 changed files with 182 additions and 169 deletions

View file

@ -22,8 +22,6 @@
[uxbox.util.router :as rt] [uxbox.util.router :as rt]
[uxbox.util.timers :as ts])) [uxbox.util.timers :as ts]))
;; --- i18n ;; --- i18n
(declare reinit) (declare reinit)

View file

@ -119,7 +119,8 @@
"settings.exit" "EXIT" "settings.exit" "EXIT"
"settings.profile.profile-saved" "Profile saved successfully!" "settings.profile.profile-saved" "Profile saved successfully!"
"settings.profile.profile.profile-saved" "Name, username and email" "settings.profile.section-basic-data" "Name, username and email"
"settings.profile.section-i18n-data" "Default language"
"settings.profile.your-name" "Your name" "settings.profile.your-name" "Your name"
"settings.profile.your-username" "Your username" "settings.profile.your-username" "Your username"
"settings.profile.your-email" "Your email" "settings.profile.your-email" "Your email"

View file

@ -119,7 +119,9 @@
"settings.exit" "QUITTER" "settings.exit" "QUITTER"
"settings.profile.profile-saved" "Profil enregistré avec succès !" "settings.profile.profile-saved" "Profil enregistré avec succès !"
"settings.profile.profile.profile-saved" "Nom, nom d'utilisateur et adresse email" "settings.profile.section-basic-data" "Nom, nom d'utilisateur et adresse email"
"settings.profile.section-i18n-data" nil ;; TODO
"settings.profile.your-name" "Votre nom complet" "settings.profile.your-name" "Votre nom complet"
"settings.profile.your-username" "Votre nom d'utilisateur" "settings.profile.your-username" "Votre nom d'utilisateur"
"settings.profile.your-email" "Votre adresse email" "settings.profile.your-email" "Votre adresse email"

View file

@ -91,9 +91,9 @@
(uuid-str? id) (uuid id) (uuid-str? id) (uuid id)
:else nil) :else nil)
type (when (str/alpha? type) (keyword type))] type (when (str/alpha? type) (keyword type))]
{:section section #js {:section section
:id id :id id
:type type})) :type type}))
(mf/def app (mf/def app
@ -105,9 +105,10 @@
:render :render
(fn [own props] (fn [own props]
(let [route (mx/react (::route-ref own))] (let [route (mx/react (::route-ref own))
(case (get-in route [:data :name]) route-id (get-in route [:data :name])]
:auth/login (mf/element auth/login-page) (case route-id
:auth/login (mf/elem auth/login-page)
:auth/register (auth/register-page) :auth/register (auth/register-page)
:auth/recovery-request (auth/recovery-request-page) :auth/recovery-request (auth/recovery-request-page)
@ -115,17 +116,22 @@
(let [token (get-in route [:params :path :token])] (let [token (get-in route [:params :path :token])]
(auth/recovery-page token)) (auth/recovery-page token))
:settings/profile (mf/element settings/profile-page) (:settings/profile
:settings/password (settings/password-page) :settings/password
:settings/notifications (settings/notifications-page) :settings/notifications)
(mf/elem settings/settings {:route route})
:dashboard/projects (dashboard/dashboard {:section :projects}) ;; :settings/profile (mf/elem settings/settings {:section :profile})
:dashboard/icons (-> (parse-dashboard-params route :icons) ;; :settings/password (mf/elem settings/settings {:section :password})
(dashboard/dashboard)) ;; :settings/notifications (mf/elem settings/notifications-page)
:dashboard/images (-> (parse-dashboard-params route :images)
(dashboard/dashboard)) :dashboard/projects (mf/elem dashboard/dashboard {:section :projects})
:dashboard/colors (-> (parse-dashboard-params route :colors) :dashboard/icons (->> (parse-dashboard-params route :icons)
(dashboard/dashboard)) (mf/element dashboard/dashboard))
:dashboard/images (->> (parse-dashboard-params route :images)
(mf/element dashboard/dashboard))
:dashboard/colors (->> (parse-dashboard-params route :colors)
(mf/element dashboard/dashboard))
:workspace/page :workspace/page
(let [project (uuid (get-in route [:params :path :project])) (let [project (uuid (get-in route [:params :path :project]))
page (uuid (get-in route [:params :path :page]))] page (uuid (get-in route [:params :path :page]))]

View file

@ -57,7 +57,7 @@
" the projects will be periodicaly wiped."]]) " the projects will be periodicaly wiped."]])
(mf/defc login-form (mf/defc login-form
{:wrap [mf/reactive]} {:wrap [mf/reactive*]}
[] []
(let [data (mf/react form-data) (let [data (mf/react form-data)
valid? (fm/valid? ::login-form data)] valid? (fm/valid? ::login-form data)]

View file

@ -6,17 +6,29 @@
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com> ;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
(ns uxbox.main.ui.settings (ns uxbox.main.ui.settings
(:require [cuerdas.core :as str] (:require
[potok.core :as ptk] [cuerdas.core :as str]
[rumext.core :as mx :include-macros true] [potok.core :as ptk]
[uxbox.builtins.icons :as i] [rumext.alpha :as mf]
[uxbox.util.dom :as dom] [uxbox.builtins.icons :as i]
[uxbox.util.router :as r] [uxbox.main.ui.messages :refer [messages-widget]]
[uxbox.main.ui.settings.profile :as profile] [uxbox.main.ui.settings.header :refer [header]]
[uxbox.main.ui.settings.password :as password] [uxbox.main.ui.settings.notifications :as notifications]
[uxbox.main.ui.settings.notifications :as notifications] [uxbox.main.ui.settings.password :as password]
[uxbox.main.ui.dashboard.header :refer (header)])) [uxbox.main.ui.settings.profile :as profile]))
(mf/defc settings
{:wrap [mf/memo*]}
[{:keys [route] :as props}]
(let [section (get-in route [:data :name])]
[:main.dashboard-main
(messages-widget)
[:& header {:section section}]
(case section
:settings/profile (mf/elem profile/profile-page)
:settings/password (mf/elem password/password-page)
:settings/notifications (mf/elem notifications/notifications-page))]))
(def profile-page profile/profile-page)
(def password-page password/password-page)
(def notifications-page notifications/notifications-page)

View file

@ -18,39 +18,33 @@
[uxbox.util.i18n :refer [tr]] [uxbox.util.i18n :refer [tr]]
[uxbox.util.router :as rt])) [uxbox.util.router :as rt]))
(def ^:private section-ref
(-> (l/in [:route :id])
(l/derive st/state)))
(mf/defc header-link (mf/defc header-link
[{:keys [section content] :as props}] [{:keys [section content] :as props}]
(let [on-click #(st/emit! (rt/nav section))] (let [on-click #(st/emit! (rt/nav section))]
[:a {:on-click on-click} content])) [:a {:on-click on-click} content]))
(mf/def header (mf/defc header
:mixins [mf/static mf/reactive] {:wrap [mf/memo*]}
:render [{:keys [section] :as props}]
(fn [own props] (let [profile? (= section :settings/profile)
(let [section (mf/react section-ref) password? (= section :settings/password)
profile? (= section :settings/profile) notifications? (= section :settings/notifications)]
password? (= section :settings/password) [:header#main-bar.main-bar
notifications? (= section :settings/notifications)] [:div.main-logo
[:header#main-bar.main-bar [:& header-link {:section :dashboard/projects
[:div.main-logo :content i/logo}]]
[:& header-link {:section :dashboard/projects [:ul.main-nav
:content i/logo}]] [:li {:class (when profile? "current")}
[:ul.main-nav [:& header-link {:section :settings/profile
[:li {:class (when profile? "current")} :content (tr "settings.profile")}]]
[:& header-link {:setion :settings/profile [:li {:class (when password? "current")}
:content (tr "settings.profile")}]] [:& header-link {:section :settings/password
[:li {:class (when password? "current")} :content (tr "settings.password")}]]
[:& header-link {:section :settings/password [:li {:class (when notifications? "current")}
:content (tr "settings.password")}]] [:& header-link {:section :settings/notifications
[:li {:class (when notifications? "current")} :content (tr "settings.notifications")}]]
[:& header-link {:section :settings/notifications #_[:li {:on-click #(st/emit! (da/logout))}
:content (tr "settings.notifications")}]] [:& header-link {:section :auth/login
[:li {:on-click #(st/emit! (da/logout))} :content (tr "settings.exit")}]]]
[:& header-link {:section :auth/login [:& user]]))
:content (tr "settings.exit")}]]]
(user)])))

View file

@ -6,45 +6,38 @@
;; Copyright (c) 2016 Juan de la Cruz <delacruzgarciajuan@gmail.com> ;; Copyright (c) 2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
(ns uxbox.main.ui.settings.notifications (ns uxbox.main.ui.settings.notifications
(:require [cuerdas.core :as str] (:require
[uxbox.util.router :as r] [cuerdas.core :as str]
[potok.core :as ptk] [rumext.alpha :as mf]
[uxbox.builtins.icons :as i] [uxbox.util.i18n :refer [tr]]))
[rumext.core :as mx :include-macros true]
[uxbox.util.i18n :refer [tr]]
[uxbox.util.dom :as dom]
[uxbox.main.ui.settings.header :refer (header)]))
(mx/defc notifications-page (mf/defc notifications-page
{:mixins [mx/static]} []
[own] [:section.dashboard-content.user-settings
[:main.dashboard-main [:section.user-settings-content
(header) [:span.user-settings-label (tr "settings.notifications.notifications-saved")]
[:section.dashboard-content.user-settings [:p (tr "settings.notifications.description")]
[:section.user-settings-content [:div.input-radio.radio-primary
[:span.user-settings-label (tr "settings.notifications.notifications-saved")] [:input {:type "radio"
[:p (tr "settings.notifications.description")] :id "notification-1"
[:div.input-radio.radio-primary :name "notification"
[:input {:type "radio" :value "none"}]
:id "notification-1" [:label {:for "notification-1"
:name "notification" :value (tr "settings.notifications.none")} (tr "settings.notifications.none")]
:value "none"}] [:input {:type "radio"
[:label {:for "notification-1" :id "notification-2"
:value (tr "settings.notifications.none")} (tr "settings.notifications.none")] :name "notification"
[:input {:type "radio" :value "every-hour"}]
:id "notification-2" [:label {:for "notification-2"
:name "notification" :value (tr "settings.notifications.every-hour")} (tr "settings.notifications.every-hour")]
:value "every-hour"}] [:input {:type "radio"
[:label {:for "notification-2" :id "notification-3"
:value (tr "settings.notifications.every-hour")} (tr "settings.notifications.every-hour")] :name "notification"
[:input {:type "radio" :value "every-day"}]
:id "notification-3" [:label {:for "notification-3"
:name "notification" :value (tr "settings.notifications.every-day")} (tr "settings.notifications.every-day")]]
:value "every-day"}] [:input.btn-primary {:type "submit"
[:label {:for "notification-3" :class "btn-disabled"
:value (tr "settings.notifications.every-day")} (tr "settings.notifications.every-day")]] :disabled true
[:input.btn-primary {:type "submit" :value (tr "settings.update-settings")}]
:class "btn-disabled" ]])
:disabled true
:value (tr "settings.update-settings")}]
]]])

View file

@ -94,11 +94,7 @@
;; --- Password Page ;; --- Password Page
(mx/defc password-page (mx/defc password-page
{:mixins [mx/static]}
[] []
[:main.dashboard-main [:section.dashboard-content.user-settings
(messages-widget) [:section.user-settings-content
(header) (password-form)]])
[:section.dashboard-content.user-settings
[:section.user-settings-content
(password-form)]]])

View file

@ -19,11 +19,11 @@
[uxbox.main.ui.messages :refer [messages-widget]] [uxbox.main.ui.messages :refer [messages-widget]]
[uxbox.main.ui.settings.header :refer [header]] [uxbox.main.ui.settings.header :refer [header]]
[uxbox.util.dom :as dom] [uxbox.util.dom :as dom]
[uxbox.util.data :refer [read-string]]
[uxbox.util.forms :as fm] [uxbox.util.forms :as fm]
[uxbox.util.i18n :refer [tr]] [uxbox.util.i18n :as i18n :refer [tr]]
[uxbox.util.interop :refer [iterable->seq]] [uxbox.util.interop :refer [iterable->seq]]
[uxbox.util.router :as r] [uxbox.util.router :as r]))
))
(def form-data (fm/focus-data :profile st/state)) (def form-data (fm/focus-data :profile st/state))
@ -63,7 +63,7 @@
;; --- Profile Form ;; --- Profile Form
(mf/def profile-form (mf/def profile-form
:mixins [mf/static mf/reactive mf/sync-render (fm/clear-mixin st/store :profile)] :mixins [mf/memo mf/reactive mf/sync-render (fm/clear-mixin st/store :profile)]
:render :render
(fn [own props] (fn [own props]
(let [data (merge {:theme "light"} (let [data (merge {:theme "light"}
@ -73,9 +73,13 @@
valid? (fm/valid? ::profile-form data) valid? (fm/valid? ::profile-form data)
theme (:theme data) theme (:theme data)
on-success #(st/emit! (clear-form)) on-success #(st/emit! (clear-form))
on-submit #(st/emit! (udu/update-profile data on-success on-error))] on-submit #(st/emit! (udu/update-profile data on-success on-error))
on-lang-change (fn [event]
(let [lang (read-string (dom/event->value event))]
(prn "on-lang-change" lang)
(i18n/set-current-locale! lang)))]
[:form.profile-form [:form.profile-form
[:span.user-settings-label (tr "settings.profile.profile.profile-saved")] [:span.user-settings-label (tr "settings.profile.section-basic-data")]
[:input.input-text [:input.input-text
{:type "text" {:type "text"
:on-change #(on-field-change % :fullname) :on-change #(on-field-change % :fullname)
@ -87,7 +91,6 @@
:value (:username data "") :value (:username data "")
:placeholder (tr "settings.profile.your-username")}] :placeholder (tr "settings.profile.your-username")}]
(fm/input-error errors :username) (fm/input-error errors :username)
[:input.input-text [:input.input-text
{:type "email" {:type "email"
:on-change #(on-field-change % :email) :on-change #(on-field-change % :email)
@ -95,6 +98,12 @@
:placeholder (tr "settings.profile.your-email")}] :placeholder (tr "settings.profile.your-email")}]
(fm/input-error errors :email) (fm/input-error errors :email)
[:span.user-settings-label (tr "settings.profile.section-i18n-data")]
[:select.input-select {:value (pr-str (mf/deref i18n/locale))
:on-change on-lang-change}
[:option {:value ":en"} "English"]
[:option {:value ":fr"} "Français"]]
[:input.btn-primary [:input.btn-primary
{:type "button" {:type "button"
:class (when-not valid? "btn-disabled") :class (when-not valid? "btn-disabled")
@ -105,7 +114,7 @@
;; --- Profile Photo Form ;; --- Profile Photo Form
(mf/defc profile-photo-form (mf/defc profile-photo-form
{:wrap [mf/reactive]} {:wrap [mf/reactive*]}
[] []
(letfn [(on-change [event] (letfn [(on-change [event]
(let [target (dom/get-target event) (let [target (dom/get-target event)
@ -128,11 +137,8 @@
(mf/defc profile-page (mf/defc profile-page
[] []
[:main.dashboard-main [:section.dashboard-content.user-settings
(messages-widget) [:section.user-settings-content
(header) [:span.user-settings-label (tr "settings.profile.your-avatar")]
[:section.dashboard-content.user-settings [:& profile-photo-form]
[:section.user-settings-content (profile-form)]])
[:span.user-settings-label (tr "settings.profile.your-avatar")]
[:& profile-photo-form]
(profile-form)]]])

View file

@ -15,6 +15,7 @@
[uxbox.main.data.lightbox :as udl] [uxbox.main.data.lightbox :as udl]
[uxbox.main.store :as st] [uxbox.main.store :as st]
[uxbox.main.ui.navigation :as nav] [uxbox.main.ui.navigation :as nav]
[uxbox.util.dom :as dom]
[uxbox.util.i18n :refer (tr)] [uxbox.util.i18n :refer (tr)]
[uxbox.util.router :as rt])) [uxbox.util.router :as rt]))
@ -22,19 +23,24 @@
(mf/defc user-menu (mf/defc user-menu
[props] [props]
[:ul.dropdown #_{:class (when-not open? "hide")} (letfn [(on-click [event section]
[:li {:on-click #(st/emit! (rt/nav :settings/profile))} (dom/stop-propagation event)
i/user (if (keyword? section)
[:span (tr "ds.user.profile")]] (st/emit! (rt/nav section))
[:li {:on-click #(st/emit! (rt/nav :settings/password))} (st/emit! section)))]
i/lock [:ul.dropdown
[:span (tr "ds.user.password")]] [:li {:on-click #(on-click % :settings/profile)}
[:li {:on-click #(st/emit! (rt/nav :settings/notifications))} i/user
i/mail [:span (tr "ds.user.profile")]]
[:span (tr "ds.user.notifications")]] [:li {:on-click #(on-click % :settings/password)}
[:li {:on-click #(st/emit! (da/logout))} i/lock
i/exit [:span (tr "ds.user.password")]]
[:span (tr "ds.user.exit")]]]) [:li {:on-click #(on-click % :settings/notifications)}
i/mail
[:span (tr "ds.user.notifications")]]
[:li {:on-click #(on-click % (da/logout))}
i/exit
[:span (tr "ds.user.exit")]]]))
;; --- User Widget ;; --- User Widget
@ -42,19 +48,18 @@
(-> (l/key :profile) (-> (l/key :profile)
(l/derive st/state))) (l/derive st/state)))
(mf/def user (mf/defc user
:mixins [mf/static mf/reactive (mf/local false)] {:wrap [mf/reactive*]}
[_]
:render (let [open (mf/use-state false)
(fn [{:keys [::mf/local] :as own} props] profile (mf/react profile-ref)
(let [profile (mf/react profile-ref) photo (if (str/empty? (:photo profile ""))
photo (if (str/empty? (:photo profile "")) "/images/avatar.jpg"
"/images/avatar.jpg" (:photo profile))]
(:photo profile))] [:div.user-zone {:on-click #(st/emit! (rt/navigate :settings/profile))
[:div.user-zone {:on-click #(st/emit! (rt/navigate :settings/profile)) :on-mouse-enter #(reset! open true)
:on-mouse-enter #(reset! local true) :on-mouse-leave #(reset! open false)}
:on-mouse-leave #(reset! local false)} [:span (:fullname profile)]
[:span (:fullname profile)] [:img {:src photo}]
[:img {:src photo}] (when @open
(when @local [:& user-menu])]))
[:& user-menu])])))

View file

@ -10,24 +10,23 @@
(:require [cuerdas.core :as str] (:require [cuerdas.core :as str]
[uxbox.util.storage :refer (storage)])) [uxbox.util.storage :refer (storage)]))
(defonce state (atom {:current-locale (get storage ::locale :en)})) (defonce locale (atom (get storage ::locale :en)))
(defonce state (atom {}))
(defn update-locales! (defn update-locales!
[callback] [callback]
(swap! state callback)) (swap! state callback))
(defn set-current-locale! (defn set-current-locale!
[locale] [v]
(swap! storage assoc ::locale locale) (swap! storage assoc ::locale v)
(swap! state assoc :current-locale locale)) (reset! locale v))
(defn on-locale-change! (defn on-locale-change!
[callback] [callback]
(add-watch state ::main (fn [_ _ old new] (add-watch locale ::main (fn [_ _ old-locale new-locale]
(let [old-locale (:current-locale old) (when (not= old-locale new-locale)
new-locale (:current-locale new)] (callback new-locale old-locale)))))
(when (not= old-locale new-locale)
(callback new-locale old-locale))))))
;; A marker type that is used just for mark ;; A marker type that is used just for mark
;; a parameter that reprsentes the counter. ;; a parameter that reprsentes the counter.
@ -50,13 +49,14 @@
"Translate the string." "Translate the string."
([t] ([t]
(let [default (name t) (let [default (name t)
locale (get @state :current-locale) locale (deref locale)
value (get-in @state [locale t] default)] value (or (get-in @state [locale t])
default)]
(if (vector? value) (if (vector? value)
(or (second value) default) (or (second value) default)
value))) value)))
([t & args] ([t & args]
(let [locale (get @state :current-locale) (let [locale (deref locale)
value (get-in @state [locale t] (name t)) value (get-in @state [locale t] (name t))
plural (first (filter c? args)) plural (first (filter c? args))
args (mapv #(if (c? %) @% %) args) args (mapv #(if (c? %) @% %) args)