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:
parent
076c29e004
commit
26cdebece4
23 changed files with 418 additions and 293 deletions
|
@ -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"}}}
|
||||
}}
|
||||
|
||||
|
|
|
@ -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))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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
|
||||
[]
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -70,4 +70,5 @@
|
|||
(when (:shapes page)
|
||||
(dom/render-to-html (page-svg page))))
|
||||
(catch :default e
|
||||
(js/console.log e)
|
||||
nil)))
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)))
|
||||
|
|
|
@ -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
|
||||
)))
|
||||
|
|
|
@ -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]}
|
||||
|
|
|
@ -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 {}
|
||||
|
|
|
@ -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 {}
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
|
||||
(defn render-to-html
|
||||
[component]
|
||||
(.renderToStatciMarkup js/ReactDOMServer component))
|
||||
(.renderToStaticMarkup js/ReactDOMServer component))
|
||||
|
||||
(defn get-element-by-class
|
||||
([classname]
|
||||
|
|
29
frontend/src/uxbox/util/html_history.cljs
Normal file
29
frontend/src/uxbox/util/html_history.cljs
Normal 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))
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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))
|
||||
|
||||
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")))
|
||||
|
|
|
@ -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"]
|
||||
|
|
Loading…
Add table
Reference in a new issue