0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-01-23 06:58:58 -05:00

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"}
funcool/promesa {:mvn/version "2.0.0"}
com.cognitect/transit-cljs {:mvn/version "0.8.239"}
funcool/promesa {:mvn/version "2.0.1"}
com.cognitect/transit-cljs {:mvn/version "0.8.256"}
funcool/rumext {:git/url "https://github.com/funcool/rumext.git",
:sha "3d598e749ba429eae6544e532fc9f81369b307f5"}
@ -12,20 +12,25 @@
environ/environ {:mvn/version "1.1.0"}
funcool/beicon {:mvn/version "3.2.0"}
funcool/bide {:mvn/version "1.6.0"}
metosin/reitit-core {:mvn/version "0.3.9"}
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/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
{:dev {:extra-deps {com.bhauman/rebel-readline-cljs {:mvn/version "0.1.4"}
com.bhauman/rebel-readline {:mvn/version "0.1.4"}
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"]}
: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
;; 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
(:require [uxbox.main.store :as st]
[uxbox.main.ui :as ui]
[uxbox.main.locales.en :as en]
[uxbox.main.locales.fr :as fr]
[uxbox.util.i18n :as i18n]))
(ns ^:figwheel-hooks uxbox.main
(:require
[rumext.core :as mx :include-macros true]
[uxbox.main.data.auth :refer [logout]]
[uxbox.main.locales.en :as en]
[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]
(-> locales
@ -19,10 +33,101 @@
(i18n/on-locale-change!
(fn [new old]
(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
[]
(st/init)
(ui/init-routes)
(ui/init))
(init-ui))
(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]
ptk/UpdateEvent
(update [_ state]
(merge state (dissoc (initial-state) :route)))
(merge state (dissoc initial-state :route :router)))
ptk/WatchEvent
(watch [this state s]
@ -59,7 +59,7 @@
:password password
:scope "webapp"}
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 logged-in)
(rx/catch rp/client-error? on-error)))))
@ -78,11 +78,12 @@
ptk/UpdateEvent
(update [_ state]
(swap! storage dissoc :auth)
(merge state (dissoc (initial-state) :route)))
(merge state (dissoc initial-state :route :router)))
ptk/WatchEvent
(watch [_ state s]
(rx/of (rt/navigate :auth/login))))
(->> (rp/req :auth/logout)
(rx/map (constantly (rt/nav :auth/login))))))
(defn logout
[]

View file

@ -12,6 +12,7 @@
[uxbox.main.store :as st]
[uxbox.main.repo :as rp]
[uxbox.util.i18n :refer [tr]]
[uxbox.util.router :as rt]
[uxbox.util.data :refer (jscoll->vec)]
[uxbox.util.uuid :as uuid]
[uxbox.util.time :as ts]
@ -80,21 +81,6 @@
[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
(defrecord CollectionsFetched [items]
@ -135,7 +121,7 @@
ptk/WatchEvent
(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
[item]
@ -213,7 +199,7 @@
(watch [_ state s]
(let [type (get-in state [:dashboard :images :type])]
(->> (rp/req :delete/image-collection id)
(rx/map #(select-collection type))))))
(rx/map #(rt/nav :dashboard/images nil {:type type}))))))
(defn delete-collection
[id]

View file

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

View file

@ -15,7 +15,7 @@
(let [url (str url "/profile/me")]
(send! {:method :get :url url})))
(defmethod request :fetch/token
(defmethod request :auth/login
[type data]
(let [url (str url "/auth/login")]
(send! {:url url
@ -23,6 +23,12 @@
:auth false
: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
[type data]
(let [params {:url (str url "/profile/me")

View file

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

View file

@ -10,7 +10,6 @@
[potok.core :as ptk]
[uxbox.builtins.colors :as colors]
[uxbox.util.storage :refer [storage]]))
(enable-console-print!)
(def ^:dynamic *on-error* identity)
@ -30,13 +29,13 @@
([event & events]
(apply ptk/emit! store (cons event events))))
(defn initial-state
[]
(def initial-state
{:dashboard {:project-order :name
:project-filter ""
:images-order :name
:images-filter ""}
:route nil
:router nil
:auth (:auth storage nil)
:clipboard #queue []
:undo {}
@ -53,6 +52,7 @@
(defn init
"Initialize the state materialization."
[]
(emit! initial-state)
(rx/to-atom store state))
([] (init {}))
([props]
(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 Juan de la Cruz <delacruzgarciajuan@gmail.com>
(ns ^:figwheel-hooks uxbox.main.ui
(ns uxbox.main.ui
(:require [beicon.core :as rx]
[lentes.core :as l]
[cuerdas.core :as str]
[bide.core :as bc]
[potok.core :as ptk]
[uxbox.builtins.icons :as i]
[uxbox.main.store :as st]
@ -24,6 +23,7 @@
[uxbox.main.ui.workspace :refer [workspace]]
[uxbox.main.ui.shapes]
[uxbox.util.messages :as uum]
[uxbox.util.html-history :as html-history]
[uxbox.util.router :as rt]
[uxbox.util.timers :as ts]
[uxbox.util.i18n :refer [tr]]
@ -33,48 +33,10 @@
;; --- Constants
(def +unrestricted+
#{:auth/login
:auth/register
:auth/recovery-request
:auth/recovery})
(def restricted?
(complement +unrestricted+))
(def route-ref
(-> (l/key :route)
(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)
(defn app-will-mount
@ -88,95 +50,45 @@
:mixins [mx/reactive]}
[]
(let [route (mx/react route-ref)
auth (mx/react st/auth-ref)
location (:id route)
params (:params route)]
(if (and (restricted? location) (not auth))
(do (ts/schedule 0 #(st/emit! (rt/navigate :auth/login))) nil)
(case location
:auth/login (auth/login-page)
:auth/register (auth/register-page)
:auth/recovery-request (auth/recovery-request-page)
:auth/recovery (auth/recovery-page (:token params))
: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))
auth (mx/react st/auth-ref)]
(prn "main$app" route)
(case (get-in route [:data :name])
:auth/login (auth/login-page)
:auth/register (auth/register-page)
:auth/recovery-request (auth/recovery-request-page)
:auth/recovery (let [token (get-in route [:params :path :token])]
(auth/recovery-page token))
:dashboard/projects (dashboard/projects-page)
;; ;; :dashboard/elements (dashboard/elements-page)
:dashboard/images (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/images-page type id))
:dashboard/icons (let [{:keys [id type]} (get-in route [:params :query])
id (cond
(str/digits? id) (parse-int id)
(uuid-str? id) (uuid id)
:else nil)
type (when (str/alpha? type) (keyword type))]
(dashboard/icons-page type id))
:dashboard/colors (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/colors-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
))))
:dashboard/images (let [{:keys [id type]} (get-in route [:params :query])
id (cond
(str/digits? id) (parse-int id)
(uuid-str? id) (uuid id)
:else nil)
type (when (str/alpha? type) (keyword type))]
(dashboard/images-page type id))
;; --- Routes
(def routes
[["/auth/login" :auth/login]
["/auth/register" :auth/register]
["/auth/recovery/request" :auth/recovery-request]
["/auth/recovery/token/:token" :auth/recovery]
["/settings/profile" :settings/profile]
["/settings/password" :settings/password]
["/settings/notifications" :settings/notifications]
["/dashboard/projects" :dashboard/projects]
["/dashboard/elements" :dashboard/elements]
["/dashboard/icons" :dashboard/icons]
["/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))
:dashboard/colors (let [{:keys [id type]} (get-in route [:params :query])
type (when (str/alpha? type) (keyword type))
id (cond
(str/digits? id) (parse-int id)
(uuid-str? id) (uuid id)
:else nil)]
(dashboard/colors-page type id))
:settings/profile (settings/profile-page)
:settings/password (settings/password-page)
:settings/notifications (settings/notifications-page)
:workspace/page (let [projectid (uuid (get-in route [:params :path :project]))
pageid (uuid (get-in route [:params :path :page]))]
(workspace projectid pageid))
nil
)))

View file

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

View file

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

View file

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

View file

@ -123,7 +123,7 @@
[:p.info {} "Download a single page of your project in SVG."]
[:select.input-select {:ref "page" :default-value (pr-str current)}
(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"]]
[:div.content-col {}
[:span.icon {} i/folder-zip]

View file

@ -48,7 +48,7 @@
pages (deref refs/selected-project-pages)
index (index-of pages page)
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" "")))))
(mx/defc header

View file

@ -27,7 +27,7 @@
(defn render-to-html
[component]
(.renderToStatciMarkup js/ReactDOMServer component))
(.renderToStaticMarkup js/ReactDOMServer component))
(defn get-element-by-class
([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
;; 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
(:require [bide.core :as r]
[beicon.core :as rx]
[potok.core :as ptk]))
(:require [reitit.core :as r]
[cuerdas.core :as str]
[potok.core :as ptk]
[uxbox.util.html-history :as html-history])
(:import goog.Uri
goog.Uri.QueryData))
(defonce +router+ nil)
;; --- Update Location (Event)
;; --- API
(deftype UpdateLocation [id params]
ptk/UpdateEvent
(update [_ state]
(let [route (merge {:id id}
(when params
{:params params}))]
(assoc state :route route))))
(defn- parse-query-data
[^QueryData qdata]
(persistent!
(reduce (fn [acc key]
(let [values (.getValues qdata key)
rkey (str/keyword key)]
(cond
(> (alength values) 1)
(assoc! acc rkey (into [] values))
(defn update-location?
[v]
(instance? UpdateLocation v))
(= (alength values) 1)
(assoc! acc rkey (aget values 0))
(defn update-location
[name params]
(UpdateLocation. name params))
:else
acc)))
(transient {})
(.getKeys qdata))))
;; --- Navigate (Event)
(deftype Navigate [id params]
ptk/EffectEvent
(effect [_ state stream]
(r/navigate! +router+ id params)))
(defn navigate
([id] (navigate id nil))
([id params]
{:pre [(keyword? id)]}
(Navigate. id params)))
;; --- Public Api
(defn- resolve-url
([router id] (resolve-url router id {} {}))
([router id params] (resolve-url router id params {}))
([router id params qparams]
(when-let [match (r/match-by-name router id params)]
(if (empty? qparams)
(r/match->path match)
(let [uri (.parse goog.Uri (r/match->path match))
qdt (.createFromMap QueryData (clj->js qparams))]
(.setQueryData uri qdt)
(.toString uri))))))
(defn init
[store routes {:keys [default] :or {default :auth/login}}]
(let [opts {:on-navigate #(ptk/emit! store (update-location %1 %2))
:default default}
router (-> (r/router routes)
(r/start! opts))]
(set! +router+ router)
router))
[routes]
(r/router routes))
(defn query-params
"Given goog.Uri, read query parameters into Clojure map."
[^goog.Uri uri]
(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
"Given a location handler and optional parameter map, return the URI
for such handler and parameters."
([id]
(if +router+
(r/resolve +router+ id)
""))
([id] (route-for id {}))
([id params]
(if +router+
(r/resolve +router+ id params)
"")))
(str (some-> +router+ (resolve-url 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>
(ns uxbox.view
(:require [uxbox.config]
[uxbox.view.store :as st]
[uxbox.view.ui :as ui]
[uxbox.main.locales.en :as en]
[uxbox.main.locales.fr :as fr]
[uxbox.util.i18n :as i18n]))
(ns ^:figwheel-hooks uxbox.view
(:require
[rumext.core :as mx :include-macros true]
[uxbox.config]
[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.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]
(-> locales
(assoc :en en/locales)
(assoc :fr fr/locales))))
(declare reinit)
(i18n/on-locale-change!
(fn [new old]
(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
[]
(st/init)
(ui/init-routes)
(ui/init))
(init-ui))
(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]
ptk/WatchEvent
(watch [_ state stream]
(let [token (get-in state [:route :params :token])]
(rx/of (rt/navigate :view/viewer {:token token :index index :id nil})))))
(let [token (get-in state [:route :params :path :token])]
(rx/of (rt/nav :view/viewer {:token token :index index})))))
(defn select-page
[index]

View file

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

View file

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

View file

@ -20,56 +20,20 @@
[rumext.core :as mx :include-macros true]
[uxbox.util.dom :as dom]))
(def route-ref
(-> (l/key :route)
(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)
(mx/defc app
{:mixins [mx/static mx/reactive]}
[]
(let [{loc :id params :params} (mx/react route-ref)]
(case loc
(let [route (mx/react route-ref)]
(prn "view$app" route)
(case (get-in route [:data :name])
: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)))
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 closure-defines
{"uxbox.config.url" (:uxbox-api-url env "http://127.0.0.1:6060/api")
"uxbox.config.viewurl" (:uxbox-view-url env "/view/")
{"uxbox.config.url" (:uxbox-api-url env "http://localhost:6060/api")
"uxbox.config.viewurl" (:uxbox-view-url env "/view/index.html")
"uxbox.config.isdemo" demo?})
(def default-build-options
{:cache-analysis true
:parallel-build true
:language-in :ecmascript6
:language-out :ecmascript5
:language-out :ecmascript6
:closure-defines closure-defines
:optimizations :none
:verbose false
@ -76,6 +76,7 @@
[args]
(figwheel/start
{:open-url false
:load-warninged-code true
:auto-testing false
:css-dirs ["resources/public/css"
"resources/public/view/css"]