feat(frontend): refactor router

This commit is contained in:
Andrey Antukh 2019-07-01 19:40:01 +02:00
parent 076c29e004
commit 26cdebece4
23 changed files with 418 additions and 293 deletions

View file

@ -1,7 +1,7 @@
{:deps {org.clojure/clojurescript {:mvn/version "1.10.516"} {:deps {org.clojure/clojurescript {:mvn/version "1.10.520"}
org.clojure/clojure {:mvn/version "1.10.1"} org.clojure/clojure {:mvn/version "1.10.1"}
funcool/promesa {:mvn/version "2.0.0"} funcool/promesa {:mvn/version "2.0.1"}
com.cognitect/transit-cljs {:mvn/version "0.8.239"} com.cognitect/transit-cljs {:mvn/version "0.8.256"}
funcool/rumext {:git/url "https://github.com/funcool/rumext.git", funcool/rumext {:git/url "https://github.com/funcool/rumext.git",
:sha "3d598e749ba429eae6544e532fc9f81369b307f5"} :sha "3d598e749ba429eae6544e532fc9f81369b307f5"}
@ -12,20 +12,25 @@
environ/environ {:mvn/version "1.1.0"} environ/environ {:mvn/version "1.1.0"}
funcool/beicon {:mvn/version "3.2.0"} metosin/reitit-core {:mvn/version "0.3.9"}
funcool/bide {:mvn/version "1.6.0"} metosin/reitit-frontend {:mvn/version "0.3.9"}
funcool/beicon {:mvn/version "5.0.0"}
funcool/bide {:mvn/version "1.6.1-SNAPSHOT"}
funcool/cuerdas {:mvn/version "2.2.0"} funcool/cuerdas {:mvn/version "2.2.0"}
funcool/lentes {:mvn/version "1.2.0"} funcool/lentes {:mvn/version "1.2.0"}
funcool/potok {:mvn/version "2.1.0"} funcool/potok {:mvn/version "2.3.0"}
} }
:paths ["src" "vendor"] :paths ["src" "vendor" "resources"]
:aliases :aliases
{:dev {:extra-deps {com.bhauman/rebel-readline-cljs {:mvn/version "0.1.4"} {:dev {:extra-deps {com.bhauman/rebel-readline-cljs {:mvn/version "0.1.4"}
com.bhauman/rebel-readline {:mvn/version "0.1.4"} com.bhauman/rebel-readline {:mvn/version "0.1.4"}
com.bhauman/figwheel-main {:mvn/version "0.2.1-SNAPSHOT"} com.bhauman/figwheel-main {:mvn/version "0.2.1-SNAPSHOT"}
org.clojure/tools.namespace {:mvn/version "0.2.11"}} org.clojure/tools.namespace {:mvn/version "0.3.0"}}
:extra-paths ["test"]} :extra-paths ["test"]}
:repl {:main-opts ["-m" "rebel-readline.main"]} :repl {:main-opts ["-m" "rebel-readline.main"]}
:ancient {:main-opts ["-m" "deps-ancient.deps-ancient"]
:extra-deps {deps-ancient {:mvn/version "RELEASE"}}}
}} }}

View file

@ -2,14 +2,28 @@
;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; 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/. ;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;; ;;
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz> ;; Copyright (c) 2015-2019 Andrey Antukh <niwi@niwi.nz>
(ns uxbox.main (ns ^:figwheel-hooks uxbox.main
(:require [uxbox.main.store :as st] (:require
[uxbox.main.ui :as ui] [rumext.core :as mx :include-macros true]
[uxbox.main.locales.en :as en] [uxbox.main.data.auth :refer [logout]]
[uxbox.main.locales.fr :as fr] [uxbox.main.locales.en :as en]
[uxbox.util.i18n :as i18n])) [uxbox.main.locales.fr :as fr]
[uxbox.main.store :as st]
[uxbox.main.ui :refer [app]]
[uxbox.main.ui.lightbox :refer [lightbox]]
[uxbox.main.ui.loader :refer [loader]]
[uxbox.util.dom :as dom]
[uxbox.util.html-history :as html-history]
[uxbox.util.i18n :as i18n :refer [tr]]
[uxbox.util.messages :as uum]
[uxbox.util.router :as rt]
[uxbox.util.timers :as ts]))
;; --- i18n
(declare reinit)
(i18n/update-locales! (fn [locales] (i18n/update-locales! (fn [locales]
(-> locales (-> locales
@ -19,10 +33,101 @@
(i18n/on-locale-change! (i18n/on-locale-change!
(fn [new old] (fn [new old]
(println "Locale changed from" old " to " new) (println "Locale changed from" old " to " new)
(ui/reinit))) (reinit)))
;; --- Error Handling
(defn- on-error
"A default error handler."
[{:keys [status] :as error}]
(js/console.error "on-error:" (pr-str error))
(js/console.error (.-stack error))
(reset! st/loader false)
(cond
;; Unauthorized or Auth timeout
(and (:status error)
(or (= (:status error) 403)
(= (:status error) 419)))
(ts/schedule 100 #(st/emit! (logout)))
;; Conflict
(= status 412)
(ts/schedule 100 #(st/emit! (uum/error (tr "errors.conflict"))))
;; Network error
(= (:status error) 0)
(ts/schedule 100 #(st/emit! (uum/error (tr "errors.network"))))
;; Something else
:else
(ts/schedule 100 #(st/emit! (uum/error (tr "errors.generic"))))))
(set! st/*on-error* on-error)
(def routes
[["/auth"
["/login" :auth/login]
["/register" :auth/register]
["/recovery/request" :auth/recovery-request]
["/recovery/token/:token" :auth/recovery]]
["/settings"
["/profile" :settings/profile]
["/password" :settings/password]
["/notifications" :settings/notifications]]
["/dashboard"
["/projects" :dashboard/projects]
["/elements" :dashboard/elements]
["/icons" :dashboard/icons]
["/images" :dashboard/images]
["/colors" :dashboard/colors]]
["/workspace/:project/:page" :workspace/page]])
(defn- on-navigate
[router path]
(let [match (rt/match router path)]
(prn "on-navigate" path match)
(cond
(and (= path "") (nil? match))
(html-history/set-path! "/dashboard/projects")
(nil? match)
(prn "TODO 404")
:else
(st/emit! #(assoc % :route match)))))
(defn init-ui
[]
(let [router (rt/init routes)
cpath (deref html-history/path)]
(st/emit! #(assoc % :router router))
(add-watch html-history/path ::main #(on-navigate router %4))
(mx/mount (app) (dom/get-element "app"))
(mx/mount (lightbox) (dom/get-element "lightbox"))
(mx/mount (loader) (dom/get-element "loader"))
(on-navigate router cpath)))
(defn ^:export init (defn ^:export init
[] []
(st/init) (st/init)
(ui/init-routes) (init-ui))
(ui/init))
(defn reinit
[]
(remove-watch html-history/path ::main)
(.unmountComponentAtNode js/ReactDOM (dom/get-element "app"))
(.unmountComponentAtNode js/ReactDOM (dom/get-element "lightbox"))
(.unmountComponentAtNode js/ReactDOM (dom/get-element "loader"))
(init-ui))
(defn ^:after-load after-load
[]
(reinit))

View file

@ -51,7 +51,7 @@
(defrecord Login [username password] (defrecord Login [username password]
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(merge state (dissoc (initial-state) :route))) (merge state (dissoc initial-state :route :router)))
ptk/WatchEvent ptk/WatchEvent
(watch [this state s] (watch [this state s]
@ -59,7 +59,7 @@
:password password :password password
:scope "webapp"} :scope "webapp"}
on-error #(rx/of (uum/error (tr "errors.auth.unauthorized")))] on-error #(rx/of (uum/error (tr "errors.auth.unauthorized")))]
(->> (rp/req :fetch/token params) (->> (rp/req :auth/login params)
(rx/map :payload) (rx/map :payload)
(rx/map logged-in) (rx/map logged-in)
(rx/catch rp/client-error? on-error))))) (rx/catch rp/client-error? on-error)))))
@ -78,11 +78,12 @@
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(swap! storage dissoc :auth) (swap! storage dissoc :auth)
(merge state (dissoc (initial-state) :route))) (merge state (dissoc initial-state :route :router)))
ptk/WatchEvent ptk/WatchEvent
(watch [_ state s] (watch [_ state s]
(rx/of (rt/navigate :auth/login)))) (->> (rp/req :auth/logout)
(rx/map (constantly (rt/nav :auth/login))))))
(defn logout (defn logout
[] []

View file

@ -12,6 +12,7 @@
[uxbox.main.store :as st] [uxbox.main.store :as st]
[uxbox.main.repo :as rp] [uxbox.main.repo :as rp]
[uxbox.util.i18n :refer [tr]] [uxbox.util.i18n :refer [tr]]
[uxbox.util.router :as rt]
[uxbox.util.data :refer (jscoll->vec)] [uxbox.util.data :refer (jscoll->vec)]
[uxbox.util.uuid :as uuid] [uxbox.util.uuid :as uuid]
[uxbox.util.time :as ts] [uxbox.util.time :as ts]
@ -80,21 +81,6 @@
[type id] [type id]
(Initialize. type id)) (Initialize. type id))
;; --- Select a Collection
(defrecord SelectCollection [type id]
ptk/WatchEvent
(watch [_ state stream]
(rx/of (r/navigate :dashboard/images
{:type type :id id}))))
(defn select-collection
([type]
(select-collection type nil))
([type id]
{:pre [(keyword? type)]}
(SelectCollection. type id)))
;; --- Color Collections Fetched ;; --- Color Collections Fetched
(defrecord CollectionsFetched [items] (defrecord CollectionsFetched [items]
@ -135,7 +121,7 @@
ptk/WatchEvent ptk/WatchEvent
(watch [_ state stream] (watch [_ state stream]
(rx/of (select-collection :own (:id item))))) (rx/of (rt/nav :dashboard/images nil {:type :own :id (:id item)}))))
(defn collection-created (defn collection-created
[item] [item]
@ -213,7 +199,7 @@
(watch [_ state s] (watch [_ state s]
(let [type (get-in state [:dashboard :images :type])] (let [type (get-in state [:dashboard :images :type])]
(->> (rp/req :delete/image-collection id) (->> (rp/req :delete/image-collection id)
(rx/map #(select-collection type)))))) (rx/map #(rt/nav :dashboard/images nil {:type type}))))))
(defn delete-collection (defn delete-collection
[id] [id]

View file

@ -70,4 +70,5 @@
(when (:shapes page) (when (:shapes page)
(dom/render-to-html (page-svg page)))) (dom/render-to-html (page-svg page))))
(catch :default e (catch :default e
(js/console.log e)
nil))) nil)))

View file

@ -15,7 +15,7 @@
(let [url (str url "/profile/me")] (let [url (str url "/profile/me")]
(send! {:method :get :url url}))) (send! {:method :get :url url})))
(defmethod request :fetch/token (defmethod request :auth/login
[type data] [type data]
(let [url (str url "/auth/login")] (let [url (str url "/auth/login")]
(send! {:url url (send! {:url url
@ -23,6 +23,12 @@
:auth false :auth false
:body data}))) :body data})))
(defmethod request :auth/logout
[type data]
(let [url (str url "/auth/logout")]
(send! {:url url :method :post :auth false})))
(defmethod request :update/profile (defmethod request :update/profile
[type data] [type data]
(let [params {:url (str url "/profile/me") (let [params {:url (str url "/profile/me")

View file

@ -21,7 +21,7 @@
(defmethod request :fetch/project-by-token (defmethod request :fetch/project-by-token
[_ token] [_ token]
(send! {:url (str url "/projects-by-token/" token) (send! {:url (str url "/projects/by-token/" token)
:method :get})) :method :get}))
(defmethod request :create/project (defmethod request :create/project

View file

@ -10,7 +10,6 @@
[potok.core :as ptk] [potok.core :as ptk]
[uxbox.builtins.colors :as colors] [uxbox.builtins.colors :as colors]
[uxbox.util.storage :refer [storage]])) [uxbox.util.storage :refer [storage]]))
(enable-console-print!) (enable-console-print!)
(def ^:dynamic *on-error* identity) (def ^:dynamic *on-error* identity)
@ -30,13 +29,13 @@
([event & events] ([event & events]
(apply ptk/emit! store (cons event events)))) (apply ptk/emit! store (cons event events))))
(defn initial-state (def initial-state
[]
{:dashboard {:project-order :name {:dashboard {:project-order :name
:project-filter "" :project-filter ""
:images-order :name :images-order :name
:images-filter ""} :images-filter ""}
:route nil :route nil
:router nil
:auth (:auth storage nil) :auth (:auth storage nil)
:clipboard #queue [] :clipboard #queue []
:undo {} :undo {}
@ -53,6 +52,7 @@
(defn init (defn init
"Initialize the state materialization." "Initialize the state materialization."
[] ([] (init {}))
(emit! initial-state) ([props]
(rx/to-atom store state)) (emit! #(merge % initial-state props))
(rx/to-atom store state)))

View file

@ -5,11 +5,10 @@
;; Copyright (c) 2015-2017 Andrey Antukh <niwi@niwi.nz> ;; Copyright (c) 2015-2017 Andrey Antukh <niwi@niwi.nz>
;; Copyright (c) 2015-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com> ;; Copyright (c) 2015-2017 Juan de la Cruz <delacruzgarciajuan@gmail.com>
(ns ^:figwheel-hooks uxbox.main.ui (ns uxbox.main.ui
(:require [beicon.core :as rx] (:require [beicon.core :as rx]
[lentes.core :as l] [lentes.core :as l]
[cuerdas.core :as str] [cuerdas.core :as str]
[bide.core :as bc]
[potok.core :as ptk] [potok.core :as ptk]
[uxbox.builtins.icons :as i] [uxbox.builtins.icons :as i]
[uxbox.main.store :as st] [uxbox.main.store :as st]
@ -24,6 +23,7 @@
[uxbox.main.ui.workspace :refer [workspace]] [uxbox.main.ui.workspace :refer [workspace]]
[uxbox.main.ui.shapes] [uxbox.main.ui.shapes]
[uxbox.util.messages :as uum] [uxbox.util.messages :as uum]
[uxbox.util.html-history :as html-history]
[uxbox.util.router :as rt] [uxbox.util.router :as rt]
[uxbox.util.timers :as ts] [uxbox.util.timers :as ts]
[uxbox.util.i18n :refer [tr]] [uxbox.util.i18n :refer [tr]]
@ -33,48 +33,10 @@
;; --- Constants ;; --- Constants
(def +unrestricted+
#{:auth/login
:auth/register
:auth/recovery-request
:auth/recovery})
(def restricted?
(complement +unrestricted+))
(def route-ref (def route-ref
(-> (l/key :route) (-> (l/key :route)
(l/derive st/state))) (l/derive st/state)))
;; --- Error Handling
(defn- on-error
"A default error handler."
[{:keys [status] :as error}]
(js/console.error "on-error:" (pr-str error))
(js/console.error (.-stack error))
(reset! st/loader false)
(cond
;; Unauthorized or Auth timeout
(and (:status error)
(or (= (:status error) 403)
(= (:status error) 419)))
(ts/schedule 100 #(st/emit! (logout)))
;; Conflict
(= status 412)
(ts/schedule 100 #(st/emit! (uum/error (tr "errors.conflict"))))
;; Network error
(= (:status error) 0)
(ts/schedule 100 #(st/emit! (uum/error (tr "errors.network"))))
;; Something else
:else
(ts/schedule 100 #(st/emit! (uum/error (tr "errors.generic"))))))
(set! st/*on-error* on-error)
;; --- Main App (Component) ;; --- Main App (Component)
(defn app-will-mount (defn app-will-mount
@ -88,95 +50,45 @@
:mixins [mx/reactive]} :mixins [mx/reactive]}
[] []
(let [route (mx/react route-ref) (let [route (mx/react route-ref)
auth (mx/react st/auth-ref) auth (mx/react st/auth-ref)]
location (:id route) (prn "main$app" route)
params (:params route)] (case (get-in route [:data :name])
(if (and (restricted? location) (not auth)) :auth/login (auth/login-page)
(do (ts/schedule 0 #(st/emit! (rt/navigate :auth/login))) nil) :auth/register (auth/register-page)
(case location :auth/recovery-request (auth/recovery-request-page)
:auth/login (auth/login-page) :auth/recovery (let [token (get-in route [:params :path :token])]
:auth/register (auth/register-page) (auth/recovery-page token))
:auth/recovery-request (auth/recovery-request-page) :dashboard/projects (dashboard/projects-page)
:auth/recovery (auth/recovery-page (:token params)) ;; ;; :dashboard/elements (dashboard/elements-page)
:dashboard/projects (dashboard/projects-page)
;; :dashboard/elements (dashboard/elements-page)
:dashboard/icons (let [{:keys [id type]} params
type (when (str/alpha? type) (keyword type))
id (cond
(str/digits? id) (parse-int id)
(uuid-str? id) (uuid id)
:else nil)]
(dashboard/icons-page type id))
:dashboard/images (let [{:keys [id type]} params :dashboard/icons (let [{:keys [id type]} (get-in route [:params :query])
type (when (str/alpha? type) (keyword type)) id (cond
id (cond (str/digits? id) (parse-int id)
(str/digits? id) (parse-int id) (uuid-str? id) (uuid id)
(uuid-str? id) (uuid id) :else nil)
:else nil)] type (when (str/alpha? type) (keyword type))]
(dashboard/images-page type id)) (dashboard/icons-page type id))
:dashboard/colors (let [{:keys [id type]} params :dashboard/images (let [{:keys [id type]} (get-in route [:params :query])
type (when (str/alpha? type) (keyword type)) id (cond
id (cond (str/digits? id) (parse-int id)
(str/digits? id) (parse-int id) (uuid-str? id) (uuid id)
(uuid-str? id) (uuid id) :else nil)
:else nil)] type (when (str/alpha? type) (keyword type))]
(dashboard/colors-page type id)) (dashboard/images-page type id))
:settings/profile (settings/profile-page)
:settings/password (settings/password-page)
:settings/notifications (settings/notifications-page)
:workspace/page (let [projectid (uuid (:project params))
pageid (uuid (:page params))]
(workspace projectid pageid))
nil
))))
;; --- Routes :dashboard/colors (let [{:keys [id type]} (get-in route [:params :query])
type (when (str/alpha? type) (keyword type))
(def routes id (cond
[["/auth/login" :auth/login] (str/digits? id) (parse-int id)
["/auth/register" :auth/register] (uuid-str? id) (uuid id)
["/auth/recovery/request" :auth/recovery-request] :else nil)]
["/auth/recovery/token/:token" :auth/recovery] (dashboard/colors-page type id))
["/settings/profile" :settings/profile] :settings/profile (settings/profile-page)
["/settings/password" :settings/password] :settings/password (settings/password-page)
["/settings/notifications" :settings/notifications] :settings/notifications (settings/notifications-page)
["/dashboard/projects" :dashboard/projects] :workspace/page (let [projectid (uuid (get-in route [:params :path :project]))
["/dashboard/elements" :dashboard/elements] pageid (uuid (get-in route [:params :path :page]))]
(workspace projectid pageid))
["/dashboard/icons" :dashboard/icons] nil
["/dashboard/icons/:type/:id" :dashboard/icons] )))
["/dashboard/icons/:type" :dashboard/icons]
["/dashboard/images" :dashboard/images]
["/dashboard/images/:type/:id" :dashboard/images]
["/dashboard/images/:type" :dashboard/images]
["/dashboard/colors" :dashboard/colors]
["/dashboard/colors/:type/:id" :dashboard/colors]
["/dashboard/colors/:type" :dashboard/colors]
["/workspace/:project/:page" :workspace/page]])
;; --- Main Entry Point
(defn init-routes
[]
(rt/init st/store routes {:default :auth/login}))
(defn ^:export init
[]
(mx/mount (app) (dom/get-element "app"))
(mx/mount (lightbox) (dom/get-element "lightbox"))
(mx/mount (loader) (dom/get-element "loader")))
(defn reinit
[]
(.unmountComponentAtNode js/ReactDOM (dom/get-element "app"))
(.unmountComponentAtNode js/ReactDOM (dom/get-element "lightbox"))
(.unmountComponentAtNode js/ReactDOM (dom/get-element "loader"))
(init))
(defn ^:after-load my-after-reload-callback []
(reinit))

View file

@ -13,7 +13,7 @@
[uxbox.builtins.icons :as i] [uxbox.builtins.icons :as i]
[uxbox.main.ui.users :as ui.u] [uxbox.main.ui.users :as ui.u]
[uxbox.util.i18n :refer (tr)] [uxbox.util.i18n :refer (tr)]
[uxbox.util.router :as r] [uxbox.util.router :as rt]
[rumext.core :as mx :include-macros true])) [rumext.core :as mx :include-macros true]))
(def header-ref (def header-ref
@ -22,8 +22,8 @@
(mx/defc header-link (mx/defc header-link
[section content] [section content]
(let [link (r/route-for section)] (let [on-click #(st/emit! (rt/navigate section))]
[:a {:href (str "/#" link)} content])) [:a {:on-click on-click} content]))
(mx/defc header (mx/defc header
{:mixins [mx/static mx/reactive]} {:mixins [mx/static mx/reactive]}

View file

@ -16,6 +16,7 @@
[uxbox.main.ui.lightbox :as lbx] [uxbox.main.ui.lightbox :as lbx]
[uxbox.main.ui.dashboard.header :refer (header)] [uxbox.main.ui.dashboard.header :refer (header)]
[uxbox.main.ui.keyboard :as kbd] [uxbox.main.ui.keyboard :as kbd]
[uxbox.util.router :as rt]
[uxbox.util.i18n :as t :refer (tr)] [uxbox.util.i18n :as t :refer (tr)]
[uxbox.util.data :refer (read-string)] [uxbox.util.data :refer (read-string)]
[rumext.core :as mx :include-macros true] [rumext.core :as mx :include-macros true]
@ -135,7 +136,7 @@
local (:rum/local own)] local (:rum/local own)]
(letfn [(on-click [event] (letfn [(on-click [event]
(let [type (or type :own)] (let [type (or type :own)]
(st/emit! (di/select-collection type id)))) (st/emit! (rt/navigate :dashboard/icons {} {:type type :id id}))))
(on-input-change [event] (on-input-change [event]
(let [value (dom/get-target event) (let [value (dom/get-target event)
value (dom/get-value value)] value (dom/get-value value)]
@ -199,8 +200,8 @@
(let [colls (->> (map second colls) (let [colls (->> (map second colls)
(filter #(= :builtin (:type %))) (filter #(= :builtin (:type %)))
(sort-by :name))] (sort-by :name))]
(st/emit! (di/select-collection type (:id (first colls))))) (st/emit! (rt/navigate :dashboard/icons {} {:type type :id (first colls)})))
(st/emit! (di/select-collection type))))] (st/emit! (rt/navigate :dashboard/icons {} {:type type}))))]
[:div.library-bar {} [:div.library-bar {}
[:div.library-bar-inside {} [:div.library-bar-inside {}
[:ul.library-tabs {} [:ul.library-tabs {}

View file

@ -17,6 +17,7 @@
[uxbox.main.ui.lightbox :as lbx] [uxbox.main.ui.lightbox :as lbx]
[uxbox.main.ui.keyboard :as kbd] [uxbox.main.ui.keyboard :as kbd]
[uxbox.main.ui.dashboard.header :refer [header]] [uxbox.main.ui.dashboard.header :refer [header]]
[uxbox.util.router :as rt]
[uxbox.util.time :as dt] [uxbox.util.time :as dt]
[uxbox.util.data :refer [read-string jscoll->vec]] [uxbox.util.data :refer [read-string jscoll->vec]]
[uxbox.util.dom :as dom])) [uxbox.util.dom :as dom]))
@ -130,7 +131,7 @@
[{:keys [rum/local] :as own} {:keys [id type name num-images] :as coll} selected?] [{:keys [rum/local] :as own} {:keys [id type name num-images] :as coll} selected?]
(letfn [(on-click [event] (letfn [(on-click [event]
(let [type (or type :own)] (let [type (or type :own)]
(st/emit! (di/select-collection type id)))) (st/emit! (rt/nav :dashboard/images {} {:type type :id id}))))
(on-input-change [event] (on-input-change [event]
(let [value (dom/get-target event) (let [value (dom/get-target event)
value (dom/get-value value)] value (dom/get-value value)]
@ -192,14 +193,14 @@
builtin? (= type :builtin)] builtin? (= type :builtin)]
(letfn [(select-tab [type] (letfn [(select-tab [type]
(if own? (if own?
(st/emit! (di/select-collection type)) (st/emit! (rt/nav :dashboard/images nil {:type type}))
(let [coll (->> (map second colls) (let [coll (->> (map second colls)
(filter #(= type (:type %))) (filter #(= type (:type %)))
(sort-by :name) (sort-by :name)
(first))] (first))]
(if coll (if coll
(st/emit! (di/select-collection type (:id coll))) (st/emit! (rt/nav :dashboard/images nil {:type type :id (:id coll)}))
(st/emit! (di/select-collection type))))))] (st/emit! (rt/nav :dashboard/images nil {:type type}))))))]
[:div.library-bar {} [:div.library-bar {}
[:div.library-bar-inside {} [:div.library-bar-inside {}
[:ul.library-tabs {} [:ul.library-tabs {}

View file

@ -123,7 +123,7 @@
[:p.info {} "Download a single page of your project in SVG."] [:p.info {} "Download a single page of your project in SVG."]
[:select.input-select {:ref "page" :default-value (pr-str current)} [:select.input-select {:ref "page" :default-value (pr-str current)}
(for [{:keys [id name]} pages] (for [{:keys [id name]} pages]
[:option {:value (pr-str id)} name])] [:option {:value (pr-str id) :key (pr-str id)} name])]
[:a.btn-primary {:href "#" :on-click download-page} "Download page"]] [:a.btn-primary {:href "#" :on-click download-page} "Download page"]]
[:div.content-col {} [:div.content-col {}
[:span.icon {} i/folder-zip] [:span.icon {} i/folder-zip]

View file

@ -48,7 +48,7 @@
pages (deref refs/selected-project-pages) pages (deref refs/selected-project-pages)
index (index-of pages page) index (index-of pages page)
rval (rand-int 1000000) rval (rand-int 1000000)
url (str cfg/viewurl "?v=" rval "#/" token "/" index)] url (str cfg/viewurl "?v=" rval "#/preview/" token "/" index)]
(st/emit! (udp/persist-page (:id page) #(js/open url "new tab" ""))))) (st/emit! (udp/persist-page (:id page) #(js/open url "new tab" "")))))
(mx/defc header (mx/defc header

View file

@ -27,7 +27,7 @@
(defn render-to-html (defn render-to-html
[component] [component]
(.renderToStatciMarkup js/ReactDOMServer component)) (.renderToStaticMarkup js/ReactDOMServer component))
(defn get-element-by-class (defn get-element-by-class
([classname] ([classname]

View file

@ -0,0 +1,29 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) 2015-2017 Andrey Antukh <niwi@niwi.nz>
(ns uxbox.util.html-history
"A singleton abstraction for the html5 fragment based history."
(:require [goog.events :as e])
(:import bide.impl.TokenTransformer
goog.history.Html5History
goog.history.EventType))
(defonce +instance+
(doto (Html5History. nil (TokenTransformer.))
(.setUseFragment true)
(.setEnabled true)))
(defonce path (atom (.getToken +instance+)))
(e/listen +instance+ EventType.NAVIGATE #(reset! path (.-token %)))
(defn set-path!
[path]
(.setToken +instance+ path))
(defn replace-path!
[path]
(.replaceToken +instance+ path))

View file

@ -2,65 +2,104 @@
;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; 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/. ;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;; ;;
;; Copyright (c) 2015-2017 Andrey Antukh <niwi@niwi.nz> ;; Copyright (c) 2015-2019 Andrey Antukh <niwi@niwi.nz>
(ns uxbox.util.router (ns uxbox.util.router
(:require [bide.core :as r] (:require [reitit.core :as r]
[beicon.core :as rx] [cuerdas.core :as str]
[potok.core :as ptk])) [potok.core :as ptk]
[uxbox.util.html-history :as html-history])
(:import goog.Uri
goog.Uri.QueryData))
(defonce +router+ nil) (defonce +router+ nil)
;; --- Update Location (Event) ;; --- API
(deftype UpdateLocation [id params] (defn- parse-query-data
ptk/UpdateEvent [^QueryData qdata]
(update [_ state] (persistent!
(let [route (merge {:id id} (reduce (fn [acc key]
(when params (let [values (.getValues qdata key)
{:params params}))] rkey (str/keyword key)]
(assoc state :route route)))) (cond
(> (alength values) 1)
(assoc! acc rkey (into [] values))
(defn update-location? (= (alength values) 1)
[v] (assoc! acc rkey (aget values 0))
(instance? UpdateLocation v))
(defn update-location :else
[name params] acc)))
(UpdateLocation. name params)) (transient {})
(.getKeys qdata))))
;; --- Navigate (Event) (defn- resolve-url
([router id] (resolve-url router id {} {}))
(deftype Navigate [id params] ([router id params] (resolve-url router id params {}))
ptk/EffectEvent ([router id params qparams]
(effect [_ state stream] (when-let [match (r/match-by-name router id params)]
(r/navigate! +router+ id params))) (if (empty? qparams)
(r/match->path match)
(defn navigate (let [uri (.parse goog.Uri (r/match->path match))
([id] (navigate id nil)) qdt (.createFromMap QueryData (clj->js qparams))]
([id params] (.setQueryData uri qdt)
{:pre [(keyword? id)]} (.toString uri))))))
(Navigate. id params)))
;; --- Public Api
(defn init (defn init
[store routes {:keys [default] :or {default :auth/login}}] [routes]
(let [opts {:on-navigate #(ptk/emit! store (update-location %1 %2)) (r/router routes))
:default default}
router (-> (r/router routes) (defn query-params
(r/start! opts))] "Given goog.Uri, read query parameters into Clojure map."
(set! +router+ router) [^goog.Uri uri]
router)) (let [q (.getQueryData uri)]
(->> q
(.getKeys)
(map (juxt keyword #(.get q %)))
(into {}))))
(defn navigate!
([router id] (navigate! router id {} {}))
([router id params] (navigate! router id params {}))
([router id params qparams]
(-> (resolve-url router id params qparams)
(html-history/set-path!))))
(defn match
"Given routing tree and current path, return match with possibly
coerced parameters. Return nil if no match found."
[router path]
(let [uri (.parse Uri path)]
(when-let [match (r/match-by-path router (.getPath uri))]
(let [qparams (parse-query-data (.getQueryData uri))
params {:path (:path-params match) :query qparams}]
(assoc match
:params params
:query-params qparams)))))
(defn route-for (defn route-for
"Given a location handler and optional parameter map, return the URI "Given a location handler and optional parameter map, return the URI
for such handler and parameters." for such handler and parameters."
([id] ([id] (route-for id {}))
(if +router+
(r/resolve +router+ id)
""))
([id params] ([id params]
(if +router+ (str (some-> +router+ (resolve-url id params)))))
(r/resolve +router+ id params)
""))) ;; --- Navigate (Event)
(deftype Navigate [id params qparams]
ptk/EffectEvent
(effect [_ state stream]
(let [router (:router state)]
(prn "Navigate:" id params qparams "| Match:" (resolve-url router id params qparams))
(navigate! router id params qparams))))
(defn nav
([id] (navigate id nil nil))
([id params] (navigate id params nil))
([id params qparams]
{:pre [(keyword? id)]}
(Navigate. id params qparams)))
(def navigate nav)

View file

@ -4,26 +4,101 @@
;; ;;
;; Copyright (c) 2016-2017 Andrey Antukh <niwi@niwi.nz> ;; Copyright (c) 2016-2017 Andrey Antukh <niwi@niwi.nz>
(ns uxbox.view (ns ^:figwheel-hooks uxbox.view
(:require [uxbox.config] (:require
[uxbox.view.store :as st] [rumext.core :as mx :include-macros true]
[uxbox.view.ui :as ui] [uxbox.config]
[uxbox.main.locales.en :as en] [uxbox.util.dom :as dom]
[uxbox.main.locales.fr :as fr] [uxbox.util.html-history :as html-history]
[uxbox.util.i18n :as i18n])) [uxbox.util.i18n :as i18n :refer [tr]]
[uxbox.util.messages :as uum]
[uxbox.util.router :as rt]
[uxbox.view.locales.en :as en]
[uxbox.view.locales.fr :as fr]
[uxbox.view.store :as st]
[uxbox.view.ui :refer [app]]
[uxbox.view.ui.lightbox :refer [lightbox]]
[uxbox.view.ui.loader :refer [loader]]))
(i18n/update-locales! (fn [locales] (i18n/update-locales! (fn [locales]
(-> locales (-> locales
(assoc :en en/locales) (assoc :en en/locales)
(assoc :fr fr/locales)))) (assoc :fr fr/locales))))
(declare reinit)
(i18n/on-locale-change! (i18n/on-locale-change!
(fn [new old] (fn [new old]
(println "Locale changed from" old " to " new) (println "Locale changed from" old " to " new)
(ui/init))) (reinit)))
(defn- on-error
"A default error handler."
[error]
(cond
;; Network error
(= (:status error) 0)
(do
(st/emit! (uum/error (tr "errors.network")))
(js/console.error "Stack:" (.-stack error)))
;; Something else
:else
(do
(st/emit! (uum/error (tr "errors.generic")))
(js/console.error "Stack:" (.-stack error)))))
(set! st/*on-error* on-error)
;; --- Routes
(def routes
[["/preview/:token/:index" :view/viewer]
["/not-found" :view/notfound]])
(defn- on-navigate
[router path]
(let [match (rt/match router path)]
(prn "on-navigate" path match)
(cond
(and (= path "") (nil? match))
(html-history/set-path! "/not-found")
(nil? match)
(prn "TODO 404")
:else
(st/emit! #(assoc % :route match)))))
(defn init-ui
[]
(let [router (rt/init routes)
cpath (deref html-history/path)]
(st/emit! #(assoc % :router router))
(add-watch html-history/path ::view #(on-navigate router %4))
(mx/mount (app) (dom/get-element "app"))
(mx/mount (lightbox) (dom/get-element "lightbox"))
(mx/mount (loader) (dom/get-element "loader"))
(on-navigate router cpath)))
(defn ^:export init (defn ^:export init
[] []
(st/init) (st/init)
(ui/init-routes) (init-ui))
(ui/init))
(defn reinit
[]
(remove-watch html-history/path ::view)
(.unmountComponentAtNode js/ReactDOM (dom/get-element "app"))
(.unmountComponentAtNode js/ReactDOM (dom/get-element "lightbox"))
(.unmountComponentAtNode js/ReactDOM (dom/get-element "loader"))
(init-ui))
(defn ^:after-load after-load
[]
(reinit))

View file

@ -76,8 +76,8 @@
(defrecord SelectPage [index] (defrecord SelectPage [index]
ptk/WatchEvent ptk/WatchEvent
(watch [_ state stream] (watch [_ state stream]
(let [token (get-in state [:route :params :token])] (let [token (get-in state [:route :params :path :token])]
(rx/of (rt/navigate :view/viewer {:token token :index index :id nil}))))) (rx/of (rt/nav :view/viewer {:token token :index index})))))
(defn select-page (defn select-page
[index] [index]

View file

@ -5,7 +5,7 @@
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz> ;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com> ;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
(ns uxbox.view.locales.en) (ns uxbox.view.locales.fr)
(defonce locales (defonce locales
{"viewer.sitemap" "plan du site" {"viewer.sitemap" "plan du site"

View file

@ -26,8 +26,7 @@
([event & events] ([event & events]
(apply ptk/emit! store (cons event events)))) (apply ptk/emit! store (cons event events))))
(defn- initial-state (def initial-state
[]
{:route nil {:route nil
:project nil :project nil
:pages nil :pages nil

View file

@ -20,56 +20,20 @@
[rumext.core :as mx :include-macros true] [rumext.core :as mx :include-macros true]
[uxbox.util.dom :as dom])) [uxbox.util.dom :as dom]))
(def route-ref (def route-ref
(-> (l/key :route) (-> (l/key :route)
(l/derive st/state))) (l/derive st/state)))
(defn- on-error
"A default error handler."
[error]
(cond
;; Network error
(= (:status error) 0)
(do
(st/emit! (uum/error (tr "errors.network")))
(js/console.error "Stack:" (.-stack error)))
;; Something else
:else
(do
(st/emit! (uum/error (tr "errors.generic")))
(js/console.error "Stack:" (.-stack error)))))
(set! st/*on-error* on-error)
;; --- Main App (Component) ;; --- Main App (Component)
(mx/defc app (mx/defc app
{:mixins [mx/static mx/reactive]} {:mixins [mx/static mx/reactive]}
[] []
(let [{loc :id params :params} (mx/react route-ref)] (let [route (mx/react route-ref)]
(case loc (prn "view$app" route)
(case (get-in route [:data :name])
:view/notfound (notfound-page) :view/notfound (notfound-page)
:view/viewer (let [{:keys [index token]} params] :view/viewer (let [{:keys [index token]} (get-in route [:params :path])]
(viewer-page token (parse-int index 0))) (viewer-page token (parse-int index 0)))
nil))) nil)))
;; --- Routes
(def routes
[["/:token/:index" :view/viewer]
["/:token" :view/viewer]
["/not-found" :view/notfound]])
;; --- Main Entry Point
(defn init-routes
[]
(rt/init st/store routes {:default :view/notfound}))
(defn init
[]
(mx/mount (app) (dom/get-element "app"))
(mx/mount (lightbox) (dom/get-element "lightbox"))
(mx/mount (loader) (dom/get-element "loader")))

View file

@ -16,15 +16,15 @@
(def demo? (boolean (:uxbox-demo env nil))) (def demo? (boolean (:uxbox-demo env nil)))
(def closure-defines (def closure-defines
{"uxbox.config.url" (:uxbox-api-url env "http://127.0.0.1:6060/api") {"uxbox.config.url" (:uxbox-api-url env "http://localhost:6060/api")
"uxbox.config.viewurl" (:uxbox-view-url env "/view/") "uxbox.config.viewurl" (:uxbox-view-url env "/view/index.html")
"uxbox.config.isdemo" demo?}) "uxbox.config.isdemo" demo?})
(def default-build-options (def default-build-options
{:cache-analysis true {:cache-analysis true
:parallel-build true :parallel-build true
:language-in :ecmascript6 :language-in :ecmascript6
:language-out :ecmascript5 :language-out :ecmascript6
:closure-defines closure-defines :closure-defines closure-defines
:optimizations :none :optimizations :none
:verbose false :verbose false
@ -76,6 +76,7 @@
[args] [args]
(figwheel/start (figwheel/start
{:open-url false {:open-url false
:load-warninged-code true
:auto-testing false :auto-testing false
:css-dirs ["resources/public/css" :css-dirs ["resources/public/css"
"resources/public/view/css"] "resources/public/view/css"]