♻️ Refactor html5 history.

This commit is contained in:
Andrey Antukh 2020-04-30 17:16:10 +02:00
parent d856b9aae3
commit 7fe7c3da6c
9 changed files with 114 additions and 128 deletions

View file

@ -20,7 +20,6 @@
[uxbox.main.ui.modal :refer [modal]] [uxbox.main.ui.modal :refer [modal]]
[uxbox.main.worker] [uxbox.main.worker]
[uxbox.util.dom :as dom] [uxbox.util.dom :as dom]
[uxbox.util.html.history :as html-history]
[uxbox.util.i18n :as i18n] [uxbox.util.i18n :as i18n]
[uxbox.util.theme :as theme] [uxbox.util.theme :as theme]
[uxbox.util.router :as rt] [uxbox.util.router :as rt]
@ -29,38 +28,33 @@
(declare reinit) (declare reinit)
(defn- on-navigate (defn on-navigate
[router path] [router path]
(let [match (rt/match router path) (let [match (rt/match router path)
profile (:profile storage)] profile (:profile storage)]
(cond (cond
(and (= path "") (not profile)) (and (= path "") (not profile))
(st/emit! (rt/nav :login)) (rt/nav :login)
(and (= path "") profile) (and (= path "") profile)
(st/emit! (rt/nav :dashboard-team {:team-id (:default-team-id profile)})) (rt/nav :dashboard-team {:team-id (:default-team-id profile)})
(nil? match) (nil? match)
(st/emit! (rt/nav :not-found)) (rt/nav :not-found)
:else :else
(st/emit! #(assoc % :route match))))) #(assoc % :route match))))
(defn init-ui (defn init-ui
[] []
(let [router (rt/init ui/routes) (st/emit! (rt/initialize-router ui/routes)
cpath (deref html-history/path)] (rt/initialize-history on-navigate))
(st/emit! #(assoc % :router router))
(add-watch html-history/path ::main #(on-navigate router %4))
(when (:profile storage) (when (:profile storage)
(st/emit! udu/fetch-profile)) (st/emit! udu/fetch-profile))
(mf/mount (mf/element ui/app) (dom/get-element "app")) (mf/mount (mf/element ui/app) (dom/get-element "app"))
(mf/mount (mf/element modal) (dom/get-element "modal")) (mf/mount (mf/element modal) (dom/get-element "modal")))
(on-navigate router cpath)))
(defn ^:export init (defn ^:export init
[] []
@ -73,7 +67,6 @@
(defn reinit (defn reinit
[] []
(remove-watch html-history/path ::main)
(mf/unmount (dom/get-element "app")) (mf/unmount (dom/get-element "app"))
(mf/unmount (dom/get-element "modal")) (mf/unmount (dom/get-element "modal"))
(init-ui)) (init-ui))

View file

@ -83,7 +83,7 @@
(group-by :backend (vals db))))) (group-by :backend (vals db)))))
(add-watch fontsdb "main" (add-watch fontsdb "main"
(fn [_ _ _ db] (fn [_ _ _ db]
(ts/schedule-on-idle #(materialize-fontsview db)))) (ts/schedule #(materialize-fontsview db))))
(defn register! (defn register!
[backend fonts] [backend fonts]

View file

@ -12,7 +12,7 @@
(:require (:require
[beicon.core :as rx] [beicon.core :as rx]
[rumext.alpha :as mf] [rumext.alpha :as mf]
[uxbox.util.timers :refer [schedule-on-idle]])) [uxbox.util.timers :refer [schedule]]))
(mf/defc chunked-list (mf/defc chunked-list
[{:keys [items children initial-size chunk-size] [{:keys [items children initial-size chunk-size]
@ -34,7 +34,7 @@
:pending-num (- pending-num chunk-size)})) :pending-num (- pending-num chunk-size)}))
(after-render [state] (after-render [state]
(when (pos? (:pending-num @state)) (when (pos? (:pending-num @state))
(let [sem (schedule-on-idle (fn [] (swap! state update-state)))] (let [sem (schedule (fn [] (swap! state update-state)))]
#(rx/cancel! sem))))] #(rx/cancel! sem))))]
(let [initial (mf/use-memo initial-state) (let [initial (mf/use-memo initial-state)

View file

@ -0,0 +1,49 @@
/**
* 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/.
*
* This Source Code Form is "Incompatible With Secondary Licenses", as
* defined by the Mozilla Public License, v. 2.0.
*
* Copyright (c) 2020 UXBOX Labs SL
*/
"use strict";
goog.provide("uxbox.util.browser_history");
goog.require("goog.history.Html5History");
goog.scope(function() {
const self = uxbox.util.browser_history;
const Html5History = goog.history.Html5History;
class TokenTransformer {
retrieveToken(pathPrefix, location) {
return location.pathname.substr(pathPrefix.length) + location.search;
}
createUrl(token, pathPrefix, location) {
return pathPrefix + token;
}
}
self.create = function() {
const instance = new Html5History(null, new TokenTransformer());
instance.setUseFragment(true);
return instance;
};
self.enable_BANG_ = function(instance) {
instance.setEnabled(true);
};
self.disable_BANG_ = function(instance) {
instance.setEnabled(false);
};
self.set_token_BANG_ = function(instance, token) {
instance.setToken(token);
}
});

View file

@ -9,7 +9,7 @@
(:require (:require
[beicon.core :as rx] [beicon.core :as rx]
[rumext.alpha :as mf] [rumext.alpha :as mf]
[uxbox.util.timers :refer [schedule-on-idle]])) [uxbox.util.timers :refer [schedule]]))
;; TODO: this file is DEPRECATED (pending deletion) ;; TODO: this file is DEPRECATED (pending deletion)
@ -33,7 +33,7 @@
:pending-num (- pending-num chunk-size)})) :pending-num (- pending-num chunk-size)}))
(after-render [state] (after-render [state]
(when (pos? (:pending-num @state)) (when (pos? (:pending-num @state))
(let [sem (schedule-on-idle (fn [] (swap! state update-state)))] (let [sem (schedule (fn [] (swap! state update-state)))]
#(rx/cancel! sem))))] #(rx/cancel! sem))))]
(let [initial (mf/use-memo initial-state) (let [initial (mf/use-memo initial-state)

View file

@ -1,60 +0,0 @@
/**
* TokenTransformer
*
* @author Paul Anderson <paul@andersonpaul.com>, 2018
* @license BSD License <https://opensource.org/licenses/BSD-2-Clause>
*/
goog.provide('uxbox.util.html.TokenTransformer');
goog.require('goog.history.Html5History');
goog.scope(function() {
/**
* A goog.history.Html5History.TokenTransformer implementation that
* includes the query string in the token.
*
* The implementation of token<->url transforms in
* `goog.history.Html5History`, when useFragment is false and no custom
* transformer is supplied, assumes that a token is equivalent to
* `window.location.pathname` minus any configured path prefix. Since
* bide allows constructing urls that include a query string, we want
* to be able to store those as tokens.
*
* Addresses funcool/bide#15.
*
* @constructor
* @implements {goog.history.Html5History.TokenTransformer}
*/
uxbox.util.html.TokenTransformer = function () {};
/**
* Retrieves a history token given the path prefix and
* `window.location` object.
*
* @param {string} pathPrefix The path prefix to use when storing token
* in a path; always begin with a slash.
* @param {Location} location The `window.location` object.
* Treat this object as read-only.
* @return {string} token The history token.
*/
uxbox.util.html.TokenTransformer.prototype.retrieveToken = function(pathPrefix, location) {
return location.pathname.substr(pathPrefix.length) + location.search;
};
/**
* Creates a URL to be pushed into HTML5 history stack when storing
* token without using hash fragment.
*
* @param {string} token The history token.
* @param {string} pathPrefix The path prefix to use when storing token
* in a path; always begin with a slash.
* @param {Location} location The `window.location` object.
* Treat this object as read-only.
* @return {string} url The complete URL string from path onwards
* (without {@code protocol://host:port} part); must begin with a
* slash.
*/
uxbox.util.html.TokenTransformer.prototype.createUrl = function(token, pathPrefix, location) {
return pathPrefix + token;
};
});

View file

@ -1,31 +0,0 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) 2019 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 uxbox.util.html.TokenTransformer
goog.history.Html5History
goog.history.EventType))
(defonce ^:private +instance+
(doto (Html5History. nil (TokenTransformer.))
(.setUseFragment true)
(.setEnabled true)))
(defonce path (atom (.getToken +instance+)))
(defonce ^:private +instance-sem+
(e/listen +instance+ EventType.NAVIGATE
#(reset! path (.-token %))))
(defn set-path!
[path]
(.setToken +instance+ path))
(defn replace-path!
[path]
(.replaceToken +instance+ path))

View file

@ -7,16 +7,19 @@
(ns uxbox.util.router (ns uxbox.util.router
(:refer-clojure :exclude [resolve]) (:refer-clojure :exclude [resolve])
(:require (:require
[beicon.core :as rx]
[rumext.alpha :as mf]
[reitit.core :as r] [reitit.core :as r]
[goog.events :as e]
[cuerdas.core :as str] [cuerdas.core :as str]
[potok.core :as ptk] [potok.core :as ptk]
[uxbox.common.data :as d] [uxbox.util.browser-history :as bhistory]
[uxbox.util.html.history :as html-history]) [uxbox.common.data :as d])
(:import (:import
goog.Uri goog.Uri
goog.Uri.QueryData)) goog.Uri.QueryData))
;; --- API ;; --- Router API
(defn- parse-query-data (defn- parse-query-data
[^QueryData qdata] [^QueryData qdata]
@ -50,10 +53,17 @@
(.setQueryData uri qdt) (.setQueryData uri qdt)
(.toString uri)))))) (.toString uri))))))
(defn init (defn create
[routes] [routes]
(r/router routes)) (r/router routes))
(defn initialize-router
[routes]
(ptk/reify ::initialize-router
ptk/UpdateEvent
(update [_ state]
(assoc state :router (create routes)))))
(defn query-params (defn query-params
"Given goog.Uri, read query parameters into Clojure map." "Given goog.Uri, read query parameters into Clojure map."
[^goog.Uri uri] [^goog.Uri uri]
@ -63,13 +73,6 @@
(map (juxt keyword #(.get q %))) (map (juxt keyword #(.get q %)))
(into {})))) (into {}))))
(defn navigate!
([router id] (navigate! router id {} {}))
([router id params] (navigate! router id params {}))
([router id params qparams]
(-> (resolve router id params qparams)
(html-history/set-path!))))
(defn match (defn match
"Given routing tree and current path, return match with possibly "Given routing tree and current path, return match with possibly
coerced parameters. Return nil if no match found." coerced parameters. Return nil if no match found."
@ -87,8 +90,10 @@
(deftype Navigate [id params qparams] (deftype Navigate [id params qparams]
ptk/EffectEvent ptk/EffectEvent
(effect [_ state stream] (effect [_ state stream]
(let [router (:router state)] (let [router (:router state)
(navigate! router id params qparams)))) history (:history state)
path (resolve router id params qparams)]
(bhistory/set-token! history path))))
(defn nav (defn nav
([id] (nav id nil nil)) ([id] (nav id nil nil))
@ -99,3 +104,30 @@
(def navigate nav) (def navigate nav)
;; --- History API
(defn initialize-history
[on-change]
(ptk/reify ::initialize-history
ptk/UpdateEvent
(update [_ state]
(let [history (bhistory/create)]
(assoc state :history history)))
ptk/WatchEvent
(watch [_ state stream]
(let [stoper (rx/filter (ptk/type? ::initialize-history) stream)
history (:history state)
router (:router state)]
(->> (rx/create (fn [sink]
(let [key (e/listen history "navigate" #(sink (.-token %)))]
(bhistory/enable! history)
(fn []
(bhistory/disable! history)
(e/unlistenByKey key)))))
(rx/map #(on-change router %))
(rx/take-until stoper))))))

View file

@ -2,7 +2,10 @@
;; 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) 2016-2019 Andrey Antukh <niwi@niwi.nz> ;; This Source Code Form is "Incompatible With Secondary Licenses", as
;; defined by the Mozilla Public License, v. 2.0.
;;
;; Copyright (c) 2020 UXBOX Labs SL
(ns uxbox.util.timers (ns uxbox.util.timers
(:require [beicon.core :as rx])) (:require [beicon.core :as rx]))