🚧 Major refactor of backend code.

Relevant changes:

- ring -> vertx
- suricatta -> vertx-pgsql
- emails improvements
- logging
- hybrid sync/async -> full async execution model
- database layout refactor
This commit is contained in:
Andrey Antukh 2019-11-18 11:52:57 +01:00
parent 73753ce071
commit e9b00339a5
134 changed files with 5394 additions and 6598 deletions

View file

@ -9,7 +9,7 @@
[beicon.core :as rx]
[clojure.set :as set]
[potok.core :as ptk]
[uxbox.main.repo :as rp]
[uxbox.main.repo.core :as rp]
[uxbox.main.store :as st]
[uxbox.util.color :as color]
[uxbox.util.i18n :refer [tr]]
@ -56,9 +56,12 @@
(defrecord FetchCollections []
ptk/WatchEvent
(watch [_ state s]
(->> (rp/req :fetch/kvstore "color-collections")
(rx/map :payload)
(rx/map collections-fetched))))
(->> (rp/query! :kvstore-entry {:key "color-collections"})
(rx/map collections-fetched)
(rx/catch (fn [{:keys [type] :as error}]
(if (= type :not-found)
(rx/empty)
(rx/throw error)))))))
(defn fetch-collections
[]
@ -99,8 +102,7 @@
data {:id "color-collections"
:version version
:value value}]
(->> (rp/req :update/kvstore data)
(rx/map :payload)
(->> (rp/mutation! :upsert-kvstore data)
(rx/map collections-fetched)))))
(defn persist-collections

View file

@ -5,17 +5,18 @@
;; Copyright (c) 2016 Andrey Antukh <niwi@niwi.nz>
(ns uxbox.main.data.icons
(:require [cuerdas.core :as str]
[beicon.core :as rx]
[uxbox.util.data :refer (jscoll->vec)]
[uxbox.util.uuid :as uuid]
[potok.core :as ptk]
[uxbox.util.i18n :refer [tr]]
[uxbox.util.router :as r]
[uxbox.util.dom :as dom]
[uxbox.util.files :as files]
[uxbox.main.store :as st]
[uxbox.main.repo :as rp]))
(:require
[beicon.core :as rx]
[cuerdas.core :as str]
[potok.core :as ptk]
[uxbox.main.repo.core :as rp]
[uxbox.main.store :as st]
[uxbox.util.data :refer (jscoll->vec)]
[uxbox.util.dom :as dom]
[uxbox.util.files :as files]
[uxbox.util.i18n :refer [tr]]
[uxbox.util.router :as r]
[uxbox.util.uuid :as uuid]))
;; --- Initialize
@ -67,8 +68,7 @@
(defrecord FetchCollections []
ptk/WatchEvent
(watch [_ state s]
(->> (rp/req :fetch/icon-collections)
(rx/map :payload)
(->> (rp/query! :icons-collections)
(rx/map collections-fetched))))
(defn fetch-collections
@ -97,9 +97,8 @@
ptk/WatchEvent
(watch [_ state s]
(let [name (tr "ds.default-library-title" (gensym "c"))
coll {:name name}]
(->> (rp/req :create/icon-collection coll)
(rx/map :payload)
data {:name name}]
(->> (rp/mutation! :create-icons-collection data)
(rx/map collection-created)))))
(defn create-collection
@ -126,9 +125,8 @@
(defrecord UpdateCollection [id]
ptk/WatchEvent
(watch [_ state s]
(let [item (get-in state [:icons-collections id])]
(->> (rp/req :update/icon-collection item)
(rx/map :payload)
(let [data (get-in state [:icons-collections id])]
(->> (rp/mutation! :update-icons-collection data)
(rx/map collection-updated)))))
(defn update-collection
@ -160,7 +158,7 @@
ptk/WatchEvent
(watch [_ state s]
(let [type (get-in state [:dashboard :icons :type])]
(->> (rp/req :delete/icon-collection id)
(->> (rp/mutation! :delete-icons-collection {:id id})
(rx/map #(select-collection type))))))
(defn delete-collection
@ -219,7 +217,7 @@
(allowed? [file]
(= (.-type file) "image/svg+xml"))
(prepare [[content metadata]]
{:collection id
{:collection-id id
:content content
:id (uuid/random)
;; TODO Keep the name of the original icon
@ -229,7 +227,7 @@
(rx/filter allowed?)
(rx/flat-map parse)
(rx/map prepare)
(rx/flat-map #(rp/req :create/icon %))
(rx/flat-map #(rp/mutation! :create-icon %))
(rx/map :payload)
(rx/map icon-created)))))
@ -255,9 +253,8 @@
(defrecord PersistIcon [id]
ptk/WatchEvent
(watch [_ state stream]
(let [icon (get-in state [:icons id])]
(->> (rp/req :update/icon icon)
(rx/map :payload)
(let [data (get-in state [:icons id])]
(->> (rp/mutation! :update-icon data)
(rx/map icon-persisted)))))
(defn persist-icon
@ -285,9 +282,8 @@
(defrecord FetchIcons [id]
ptk/WatchEvent
(watch [_ state s]
(let [params {:coll id}]
(->> (rp/req :fetch/icons params)
(rx/map :payload)
(let [params (cond-> {} id (assoc :collection-id id))]
(->> (rp/query! :icons-by-collection params)
(rx/map icons-fetched)))))
(defn fetch-icons
@ -306,7 +302,7 @@
ptk/WatchEvent
(watch [_ state s]
(->> (rp/req :delete/icon id)
(->> (rp/mutation! :delete-icon {:id id})
(rx/ignore))))
(defn delete-icon
@ -370,8 +366,8 @@
(->> (rx/from-coll selected)
(rx/map #(get-in state [:icons %]))
(rx/map #(dissoc % :id))
(rx/map #(assoc % :collection id))
(rx/flat-map #(rp/req :create/icon %))
(rx/map #(assoc % :collection-id id))
(rx/flat-map #(rp/mutation :create-icon %))
(rx/map :payload)
(rx/map icon-created))
(->> (rx/from-coll selected)

View file

@ -5,20 +5,21 @@
;; Copyright (c) 2016 Andrey Antukh <niwi@niwi.nz>
(ns uxbox.main.data.images
(:require [cljs.spec.alpha :as s]
[cuerdas.core :as str]
[beicon.core :as rx]
[potok.core :as ptk]
[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]
[uxbox.util.spec :as us]
[uxbox.util.router :as r]
[uxbox.util.files :as files]))
(:require
[cljs.spec.alpha :as s]
[cuerdas.core :as str]
[beicon.core :as rx]
[potok.core :as ptk]
[uxbox.main.store :as st]
[uxbox.main.repo.core :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]
[uxbox.util.spec :as us]
[uxbox.util.router :as r]
[uxbox.util.files :as files]))
;; --- Specs
@ -30,21 +31,20 @@
(s/def ::mimetype string?)
(s/def ::thumbnail us/url-str?)
(s/def ::id uuid?)
(s/def ::version integer?)
(s/def ::url us/url-str?)
(s/def ::collection (s/nilable uuid?))
(s/def ::user uuid?)
(s/def ::collection-id (s/nilable ::us/uuid))
(s/def ::user-id ::us/uuid)
(s/def ::collection-entity
(s/keys :req-un [::id
::name
::created-at
::modified-at
::user
::version]))
::user-id]))
(s/def ::image-entity
(s/keys :req-un [::id
(s/keys :opt-un [::collection-id]
:req-un [::id
::name
::width
::height
@ -53,9 +53,7 @@
::mimetype
::thumbnail
::url
::version
::collection
::user]))
::user-id]))
;; --- Initialize
@ -90,8 +88,7 @@
(defrecord FetchCollections []
ptk/WatchEvent
(watch [_ state s]
(->> (rp/req :fetch/image-collections)
(rx/map :payload)
(->> (rp/query! :images-collections)
(rx/map collections-fetched))))
(defn fetch-collections
@ -120,9 +117,8 @@
(defrecord CreateCollection []
ptk/WatchEvent
(watch [_ state s]
(let [coll {:name (tr "ds.default-library-title" (gensym "c"))
:id (uuid/random)}]
(->> (rp/req :create/image-collection coll)
(let [data {:name (tr "ds.default-library-title" (gensym "c"))}]
(->> (rp/mutation! :create-image-collection data)
(rx/map :payload)
(rx/map collection-created)))))
@ -152,8 +148,7 @@
ptk/WatchEvent
(watch [_ state s]
(let [item (get-in state [:images-collections id])]
(->> (rp/req :update/image-collection item)
(rx/map :payload)
(->> (rp/mutation! :update-images-collection item)
(rx/map collection-updated)))))
(defn update-collection
@ -185,7 +180,7 @@
ptk/WatchEvent
(watch [_ state s]
(let [type (get-in state [:dashboard :images :type])]
(->> (rp/req :delete/image-collection id)
(->> (rp/mutation! :delete-images-collection {:id id})
(rx/map #(rt/nav :dashboard/images nil {:type type}))))))
(defn delete-collection
@ -223,17 +218,18 @@
(finalize-upload [state]
(assoc-in state [:dashboard :images :uploading] false))
(prepare [[file [width height]]]
{:collection id
:mimetype (.-type file)
:id (uuid/random)
:file file
:width width
:height height})]
(cond-> {:name (.-name file)
:mimetype (.-type file)
:id (uuid/random)
:file file
:width width
:height height}
id (assoc :collection-id id)))]
(->> (rx/from-coll files)
(rx/filter allowed-file?)
(rx/mapcat image-size)
(rx/map prepare)
(rx/mapcat #(rp/req :create/image %))
(rx/mapcat #(rp/mutation! :create-image %))
(rx/map :payload)
(rx/reduce conj [])
(rx/do #(st/emit! finalize-upload))
@ -266,9 +262,8 @@
(defrecord PersistImage [id]
ptk/WatchEvent
(watch [_ state stream]
(let [image (get-in state [:images id])]
(->> (rp/req :update/image image)
(rx/map :payload)
(let [data (get-in state [:images id])]
(->> (rp/mutation! :update-image data)
(rx/map image-persisted)))))
(defn persist-image
@ -295,9 +290,8 @@
(defrecord FetchImages [id]
ptk/WatchEvent
(watch [_ state s]
(let [params {:coll id}]
(->> (rp/req :fetch/images params)
(rx/map :payload)
(let [params (cond-> {} id (assoc :collection-id id))]
(->> (rp/query! :images-by-collection params)
(rx/map images-fetched)))))
(defn fetch-images
@ -316,10 +310,9 @@
(let [existing (get-in state [:images id])]
(if existing
(rx/empty)
(->> (rp/req :fetch/image {:id id})
(rx/catch rp/client-error? #(rx/empty))
(rx/map :payload)
(rx/map image-fetched))))))
(->> (rp/query! :image-by-id {:id id})
(rx/map image-fetched)
(rx/catch rp/client-error? #(rx/empty)))))))
(defn fetch-image
"Conditionally fetch image by its id. If image
@ -352,7 +345,7 @@
ptk/WatchEvent
(watch [_ state s]
(->> (rp/req :delete/image id)
(->> (rp/mutation! :delete-image {:id id})
(rx/ignore))))
(defn delete-image
@ -414,8 +407,7 @@
(let [selected (get-in state [:dashboard :images :selected])]
(rx/merge
(->> (rx/from-coll selected)
(rx/flat-map #(rp/req :copy/image {:id % :collection id}))
(rx/map :payload)
(rx/flat-map #(rp/mutation! :copy-image {:id % :collection-id id}))
(rx/map image-created))
(->> (rx/from-coll selected)
(rx/map deselect-image))))))

View file

@ -22,7 +22,7 @@
(s/def ::name ::us/string)
(s/def ::inst ::us/inst)
(s/def ::type ::us/keyword)
(s/def ::project ::us/uuid)
(s/def ::project-id ::us/uuid)
(s/def ::created-at ::us/inst)
(s/def ::modified-at ::us/inst)
(s/def ::version ::us/number)
@ -51,10 +51,10 @@
(s/def ::page-entity
(s/keys :req-un [::id
::name
::project
::project-id
::created-at
::modified-at
::user
::user-id
::metadata
::shapes]))
@ -70,7 +70,7 @@
(s/def ::server-page
(s/keys :req-un [::id ::name
::project
::project-id
::version
::created-at
::modified-at
@ -194,7 +194,7 @@
(declare rehash-pages)
(s/def ::page-created-params
(s/keys :req-un [::id ::name ::project ::metadata]))
(s/keys :req-un [::id ::name ::project-id ::metadata]))
(defn page-created
[data]
@ -215,7 +215,7 @@
;; --- Create Page Form
(s/def ::form-created-page-params
(s/keys :req-un [::name ::project ::width ::height]))
(s/keys :req-un [::name ::project-id ::width ::height]))
(defn form->create-page
[{:keys [name project width height layout] :as data}]

View file

@ -22,7 +22,7 @@
(s/def ::id uuid?)
(s/def ::name string?)
(s/def ::version integer?)
(s/def ::user uuid?)
(s/def ::user-id uuid?)
(s/def ::created-at inst?)
(s/def ::modified-at inst?)
@ -30,7 +30,7 @@
(s/keys ::req-un [::id
::name
::version
::user
::user-id
::created-at
::modified-at]))

View file

@ -9,7 +9,7 @@
[cljs.spec.alpha :as s]
[beicon.core :as rx]
[potok.core :as ptk]
[uxbox.main.repo :as rp]
[uxbox.main.repo.core :as rp]
[uxbox.util.i18n :as i18n :refer [tr]]
[uxbox.util.messages :as uum]
[uxbox.util.spec :as us]
@ -59,8 +59,7 @@
(reify
ptk/WatchEvent
(watch [_ state s]
(->> (rp/req :fetch/profile)
(rx/map :payload)
(->> (rp/query! :profile)
(rx/map profile-fetched)))))
;; --- Update Profile

View file

@ -11,7 +11,6 @@
[potok.core :as ptk]
[uxbox.config :as cfg]
[uxbox.main.constants :as c]
[uxbox.main.data.history :as udh]
[uxbox.main.data.icons :as udi]
[uxbox.main.data.pages :as udp]
[uxbox.main.data.projects :as dp]

View file

@ -12,21 +12,20 @@
(defmethod request :fetch/profile
[type _]
(let [url (str url "/profile/me")]
(let [url (str url "/w/query/profile")]
(send! {:method :get :url url})))
(defmethod request :auth/login
[type data]
(let [url (str url "/auth/login")]
(let [url (str url "/login")]
(send! {:url url
:method :post
:auth false
:body data})))
:method :post
:auth false
:body data})))
(defmethod request :auth/logout
[type data]
(let [url (str url "/auth/logout")]
(let [url (str url "/logout")]
(send! {:url url :method :post :auth false})))
(defmethod request :update/profile

View file

@ -0,0 +1,135 @@
;; 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.main.repo.core
(:require
[beicon.core :as rx]
[cuerdas.core :as str]
[uxbox.main.repo.impl :as impl]
[uxbox.config :refer [url]]
[uxbox.util.http :as http]
[uxbox.util.storage :refer [storage]]
[uxbox.util.transit :as t])
(:import [goog.Uri QueryData]))
;; --- Low Level API
(defn- conditional-decode
[{:keys [body headers] :as response}]
(let [contentype (get headers "content-type")]
(if (str/starts-with? contentype "application/transit+json")
(assoc response :body (t/decode body))
response)))
(defn- handle-http-status
[{:keys [body status] :as response}]
(if (http/success? response)
(rx/of {:status status :payload body})
(rx/throw {:status status :payload body})))
(def ^:private +headers+
{"content-type" "application/transit+json"})
(defn- encode-query
[params]
(let [data (QueryData.)]
(.extend data (clj->js params))
(.toString data)))
(defn impl-send
[{:keys [body headers auth method query url response-type]
:or {auth true response-type :text}}]
(let [headers (merge {"Accept" "application/transit+json,*/*"}
(when (map? body) +headers+)
headers)
request {:method method
:url url
:headers headers
:query-string (when query (encode-query query))
:body (if (map? body) (t/encode body) body)}
options {:response-type response-type
:credentials? true}]
(http/send! request options)))
(defn send!
[request]
(->> (impl-send request)
(rx/map conditional-decode)
(rx/mapcat handle-http-status)))
;; --- High Level API
(defn- handle-response
[response]
;; (prn "handle-response1" response)
(cond
(http/success? response)
(rx/of (:body response))
(http/client-error? response)
(rx/throw (:body response))
:else
(rx/throw {:type :unexpected
:code (:error response)})))
(defn send-query!
[id params]
(let [url (str url "/w/query/" (name id))]
(->> (impl-send {:method :get :url url :query params})
(rx/map conditional-decode)
(rx/mapcat handle-response))))
(defn send-mutation!
[id params]
(let [url (str url "/w/mutation/" (name id))]
(send! {:method :post
:url url
:body params})))
(defn- dispatch
[& args]
(first args))
(defmulti query dispatch)
(defmulti mutation dispatch)
(defmethod query :default
[id params]
(send-query! id params))
(defmethod mutation :default
[id params]
(send-mutation! id params))
(defn query!
([id] (query id {}))
([id params] (query id params)))
(defn mutation!
([id] (mutation id {}))
([id params] (mutation id params)))
;; --- Legacy Api
(defn req
"Perform a side effectfull action accesing
remote resources."
([type]
(impl/request type nil))
([type data]
(impl/request type data)))
(def client-error? http/client-error?)
(def server-error? http/server-error?)
(defmethod mutation :create-image
[id params]
(let [form (js/FormData.)]
(run! (fn [[key val]]
(.append form (name key) val))
(seq params))
(send-mutation! id form)))

View file

@ -8,24 +8,19 @@
"A main interface for access to remote resources."
(:require
[uxbox.config :refer [url]]
[uxbox.main.repo.impl :refer [request send!]]))
(defmethod request :fetch/pages
[type data]
(let [params {:url (str url "/pages")
:method :get}]
(send! params)))
[uxbox.main.repo.impl :as rp :refer [request send!]]))
(defmethod request :fetch/pages-by-project
[type {:keys [project] :as params}]
(let [url (str url "/pages")
params {:project project}]
(let [url (str url "/w/query/pages-by-project")
params {:project-id project}]
(send! {:method :get :url url :query params})))
(defmethod request :fetch/page-history
[type {:keys [page] :as params}]
(let [url (str url "/pages/" page "/history")
query (select-keys params [:max :since :pinned])
(let [url (str url "/w/query/page-history")
query (-> (select-keys params [:max :since :pinned])
(assoc :id page))
params {:method :get :url url :query query}]
(send! params)))
@ -44,22 +39,22 @@
(defmethod request :update/page
[type {:keys [id] :as body}]
(let [params {:url (str url "/pages/" id)
:method :put
(let [params {:url (str url "/w/mutation/update-page")
:method :post
:body body}]
(send! params)))
(defmethod request :update/page-metadata
[type {:keys [id] :as body}]
(let [params {:url (str url "/w/mutation/update-page-metadata")
:method :post
:body body}]
(send! params)))
(defmethod request :update/page-history
[type {:keys [id page] :as data}]
(let [params {:url (str url "/pages/" page "/history/" id)
:method :put
:body data}]
(send! params)))
(defmethod request :update/page-metadata
[type {:keys [id metadata] :as body}]
(let [body (dissoc body :data)
params {:url (str url "/pages/" id "/metadata")
:method :put
:body body}]
(send! params)))

View file

@ -16,7 +16,7 @@
[type data]
;; Obtain the list of projects and decode the embedded
;; page data in order to have it usable.
(send! {:url (str url "/projects")
(send! {:url (str url "/w/query/projects")
:method :get}))
(defmethod request :fetch/project-by-token

View file

@ -12,6 +12,7 @@
[lentes.core :as l]
[potok.core :as ptk]
[rumext.alpha :as mf]
[expound.alpha :as expound]
[uxbox.builtins.icons :as i]
[uxbox.main.data.auth :refer [logout]]
[uxbox.main.data.projects :as dp]
@ -53,31 +54,37 @@
(defn- on-error
"A default error handler."
[{:keys [status] :as error}]
(js/console.error "Unhandled Error:"
"\n - message:" (ex-message error)
"\n - data:" (pr-str (ex-data error)))
(js/console.error error)
[{:keys [type code] :as error}]
(reset! st/loader false)
(cond
;; Unauthorized or Auth timeout
(and (:status error)
(or (= (:status error) 403)
(= (:status error) 419)))
(and (map? error)
(= :validation type)
(= :spec-validation code))
(do
(println "============ SERVER RESPONSE ERROR ================")
(println (:explain error))
(println "============ END SERVER RESPONSE ERROR ================"))
;; Unauthorized or Auth timeout
(and (map? error)
(= :authentication type)
(= :unauthorized code))
(ts/schedule 0 #(st/emit! (rt/nav :auth/login)))
;; Conflict
(= status 412)
(ts/schedule 100 #(st/emit! (uum/error (tr "errors.conflict"))))
;; Network error
(= (:status error) 0)
(and (map? error)
(= :unexpected type)
(= :abort code))
(ts/schedule 100 #(st/emit! (uum/error (tr "errors.network"))))
;; Something else
:else
(ts/schedule 100 #(st/emit! (uum/error (tr "errors.generic"))))))
(do
(js/console.error "Unhandled Error:"
"\n - message:" (ex-message error)
"\n - data:" (pr-str (ex-data error)))
(js/console.error error)
(ts/schedule 100 #(st/emit! (uum/error (tr "errors.generic")))))))
(set! st/*on-error* on-error)

View file

@ -83,7 +83,7 @@
[id]
(letfn [(selector [icons]
(->> (vals icons)
(filter #(= id (:collection %)))
(filter #(= id (:collection-id %)))
(count)))]
(-> (comp (l/key :icons)
(l/lens selector))
@ -325,7 +325,7 @@
(-> (comp (l/key :icons)
(l/lens (fn [icons]
(->> (vals icons)
(filter #(= id (:collection %)))))))
(filter #(= id (:collection-id %)))))))
(l/derive st/state)))
(mf/defc grid

View file

@ -84,7 +84,7 @@
[id]
(letfn [(selector [images]
(->> (vals images)
(filter #(= id (:collection %)))
(filter #(= id (:collection-id %)))
(count)))]
(-> (comp (l/key :images)
(l/lens selector))
@ -310,7 +310,7 @@
(-> (comp (l/key :images)
(l/lens (fn [images]
(->> (vals images)
(filter #(= id (:collection %)))))))
(filter #(= id (:collection-id %)))))))
(l/derive st/state)))
(mf/defc grid

View file

@ -63,8 +63,9 @@
[canvas page]
(st/emit! (udp/watch-page-changes (:id page))
(udu/watch-page-changes (:id page))
(udh/initialize (:id page))
(udh/watch-page-changes (:id page))
;; TODO: temporary commented
;; (udh/initialize (:id page))
;; (udh/watch-page-changes (:id page))
(dw/start-shapes-watcher (:id page)))
(let [sub (shortcuts/init)]
#(do (st/emit! ::udp/stop-page-watcher

View file

@ -24,7 +24,7 @@
[:aside.settings-bar.settings-bar-left
[:div.settings-bar-inside
(when (contains? flags :sitemap)
[:& sitemap-toolbox {:project-id (:project page)
[:& sitemap-toolbox {:project-id (:project-id page)
:current-page-id (:id page)
:page page}])
(when (contains? flags :document-history)

View file

@ -37,14 +37,14 @@
(dom/stop-propagation event)
(modal/show! confirm-dialog {:on-accept delete}))
(on-drop [item monitor]
(st/emit! (udp/rehash-pages (:project page))))
(st/emit! (udp/rehash-pages (:project-id page))))
(on-hover [item monitor]
(st/emit! (udp/move-page {:project-id (:project-id item)
:page-id (:page-id item)
:index index})))]
(let [[dprops ref] (use-sortable {:type "page-item"
:data {:page-id (:id page)
:project-id (:project page)
:project-id (:project-id page)
:index index}
:on-hover on-hover
:on-drop on-drop})]
@ -52,7 +52,7 @@
[:div.element-list-body
{:class (classnames :selected selected?
:dragging (:dragging? dprops))
:on-click #(st/emit! (rt/nav :workspace/page {:project (:project page)
:on-click #(st/emit! (rt/nav :workspace/page {:project (:project-id page)
:page (:id page)}))
:on-double-click #(dom/stop-propagation %)
:draggable true}
@ -92,6 +92,7 @@
(mf/defc sitemap-toolbox
[{:keys [project-id current-page-id] :as props}]
(prn "sitemap-toolbox" props)
(let [project-iref (mf/use-memo {:deps #js [project-id]
:fn #(-> (l/in [:projects project-id])
(l/derive st/state))})

View file

@ -203,3 +203,15 @@
;; (let [keys# (map #(keyword (name %)) fields)
;; vals# fields]
;; (apply hash-map (interleave keys# vals#))))
;; (defmacro some->'
;; [x & forms]
;; `(let [x# (p/then' ~x (fn [v#]
;; (when (nil? v#)
;; (throw (ex-info "internal" {::some-interrupt true})))
;; v#))]
;; (-> (-> x# ~@forms)
;; (p/catch' (fn [e#]
;; (if (::some-interrupt (ex-data e#))
;; nil
;; (throw e#)))))))

View file

@ -40,7 +40,9 @@
ErrorCode.TIMEOUT :timeout
ErrorCode.EXCEPTION :exception
ErrorCode.HTTP_ERROR :http
ErrorCode.ABORT :abort))
ErrorCode.ABORT :abort
ErrorCode.OFFLINE :offline
nil))
(defn- translate-response-type
[type]
@ -72,18 +74,18 @@
(rx/create
(fn [sink]
(letfn [(on-complete [event]
(if (or (= (.getLastErrorCode xhr) ErrorCode.HTTP_ERROR)
(.isSuccess xhr))
(sink (rx/end
{:status (.getStatus xhr)
:body (.getResponse xhr)
:headers (normalize-headers
(.getResponseHeaders xhr))}))
(sink (let [type (-> (.getLastErrorCode xhr)
(translate-error-code))
message (.getLastError xhr)]
(ex-info message {:type type})))))]
(let [type (translate-error-code (.getLastErrorCode xhr))
status (.getStatus xhr)]
;; (prn "on-complete" type method url)
(if (pos? status)
(sink (rx/end
{:status status
:body (.getResponse xhr)
:headers (normalize-headers (.getResponseHeaders xhr))}))
(sink (rx/end
{:status 0
:error (if (= type :http) :abort type)
::xhr xhr})))))]
(events/listen xhr EventType.COMPLETE on-complete)
(.send xhr uri method body headers)
#(.abort xhr))))))