Merge pull request #193 from uxbox/other/fixes-and-performance-improvements

Other/fixes and performance improvements
This commit is contained in:
Hirunatan 2020-05-04 13:17:24 +02:00 committed by GitHub
commit 37d0f20b7e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
32 changed files with 600 additions and 1387 deletions

View file

@ -89,7 +89,8 @@
(defn- retrieve-profile-by-email (defn- retrieve-profile-by-email
[conn email] [conn email]
(db/query-one conn [sql:profile-by-email email])) (-> (db/query-one conn [sql:profile-by-email email])
(p/then #(images/resolve-media-uris % [:photo :photo-uri]))))
;; --- Mutation: Update Profile (own) ;; --- Mutation: Update Profile (own)

View file

@ -94,4 +94,4 @@
(defn strip-private-attrs (defn strip-private-attrs
"Only selects a publicy visible profile attrs." "Only selects a publicy visible profile attrs."
[profile] [profile]
(select-keys profile [:id :fullname :lang :email :created-at :photo :theme])) (select-keys profile [:id :fullname :lang :email :created-at :photo :theme :photo-uri]))

View file

@ -17,7 +17,7 @@
funcool/okulary {:mvn/version "2020.04.14-0"} funcool/okulary {:mvn/version "2020.04.14-0"}
funcool/potok {:mvn/version "2.8.0-SNAPSHOT"} funcool/potok {:mvn/version "2.8.0-SNAPSHOT"}
funcool/promesa {:mvn/version "5.1.0"} funcool/promesa {:mvn/version "5.1.0"}
funcool/rumext {:mvn/version "2020.04.14-1"} funcool/rumext {:mvn/version "2020.05.04-0"}
} }
:aliases :aliases
{:dev {:dev

File diff suppressed because it is too large Load diff

View file

@ -8,17 +8,17 @@
;; Copyright (c) 2016-2020 Andrey Antukh <niwi@niwi.nz> ;; Copyright (c) 2016-2020 Andrey Antukh <niwi@niwi.nz>
(ns uxbox.config (ns uxbox.config
(:require [goog.object :as gobj])) (:require [uxbox.util.object :as obj]))
(defn- get-current-origin (defn- get-current-origin
[] []
(let [location (gobj/get goog.global "location")] (let [location (obj/get goog.global "location")]
(gobj/get location "origin"))) (obj/get location "origin")))
(let [config (gobj/get goog.global "uxboxConfig") (let [config (obj/get goog.global "uxboxConfig")
public-url (gobj/get config "publicURL" "http://localhost:6060")] public-url (obj/get config "publicURL" "http://localhost:6060")]
(def default-language "en") (def default-language "en")
(def demo-warning (gobj/get config "demoWarning" true)) (def demo-warning (obj/get config "demoWarning" true))
(def url public-url) (def url public-url)
(def default-theme "default")) (def default-theme "default"))

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)) (when (:profile storage)
(add-watch html-history/path ::main #(on-navigate router %4)) (st/emit! udu/fetch-profile))
(when (:profile storage) (mf/mount (mf/element ui/app) (dom/get-element "app"))
(st/emit! udu/fetch-profile)) (mf/mount (mf/element modal) (dom/get-element "modal")))
(mf/mount (mf/element ui/app) (dom/get-element "app"))
(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

@ -65,7 +65,7 @@
(ptk/reify ::clear-user-data (ptk/reify ::clear-user-data
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(select-keys state [:route :router :session-id])) (select-keys state [:route :router :session-id :history]))
ptk/WatchEvent ptk/WatchEvent
(watch [_ state s] (watch [_ state s]

View file

@ -141,7 +141,9 @@
ptk/WatchEvent ptk/WatchEvent
(watch [_ state stream] (watch [_ state stream]
(->> (rp/query :projects-by-team {:team-id team-id}) (->> (rp/query :projects-by-team {:team-id team-id})
(rx/map projects-fetched))))) (rx/map projects-fetched)
(rx/catch (fn [error]
(rx/of (rt/nav' :not-authorized))))))))
(defn projects-fetched (defn projects-fetched
[projects] [projects]
@ -208,7 +210,9 @@
(watch [_ state stream] (watch [_ state stream]
(let [params {:team-id team-id}] (let [params {:team-id team-id}]
(->> (rp/query :recent-files params) (->> (rp/query :recent-files params)
(rx/map recent-files-fetched)))))) (rx/map recent-files-fetched)
(rx/catch (fn [e]
(rx/of (rt/nav' :not-authorized)))))))))
(defn recent-files-fetched (defn recent-files-fetched
[recent-files] [recent-files]

View file

@ -72,7 +72,8 @@
(ptk/reify ::finalize (ptk/reify ::finalize
ptk/WatchEvent ptk/WatchEvent
(watch [_ state stream] (watch [_ state stream]
(ws/-close (get-in state [:ws file-id])) (when-let [ws (get-in state [:ws file-id])]
(ws/-close ws))
(rx/of ::finalize)))) (rx/of ::finalize))))
;; --- Handle: Presence ;; --- Handle: Presence

View file

@ -140,9 +140,17 @@
(rx/first) (rx/first)
(rx/map (fn [[file users project pages]] (rx/map (fn [[file users project pages]]
(bundle-fetched file users project pages))) (bundle-fetched file users project pages)))
(rx/catch (fn [{:keys [type] :as error}] (rx/catch (fn [{:keys [type code] :as error}]
(when (= :not-found type) (cond
(rx/of (rt/nav :not-found))))))))) (= :not-found type)
(rx/of (rt/nav' :not-found))
(and (= :authentication type)
(= :unauthorized code))
(rx/of (rt/nav' :not-authorized))
:else
(throw error))))))))
(defn- bundle-fetched (defn- bundle-fetched
[file users project pages] [file users project pages]

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

@ -1,44 +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/.
;;
;; This Source Code Form is "Incompatible With Secondary Licenses", as
;; defined by the Mozilla Public License, v. 2.0.
;;
;; Copyright (c) 2020 UXBOX Labs S.L
(ns uxbox.main.ui.components.chunked-list
"A collection of general purpose utility components."
(:require
[beicon.core :as rx]
[rumext.alpha :as mf]
[uxbox.util.timers :refer [schedule-on-idle]]))
(mf/defc chunked-list
[{:keys [items children initial-size chunk-size]
:or {initial-size 30 chunk-size 5}
:as props}]
(letfn [(initial-state []
(let [total (count items)
size (if (> total initial-size) initial-size total)
current (take size items)
pending (drop size items)]
{:current (vec current)
:pending pending
:pending-num (- total size)}))
(update-state [{:keys [current pending pending-num] :as state}]
(let [chunk-size (if (> pending-num chunk-size) chunk-size pending-num)]
{:current (into current (take chunk-size pending))
:pending (drop chunk-size pending)
:pending-num (- pending-num chunk-size)}))
(after-render [state]
(when (pos? (:pending-num @state))
(let [sem (schedule-on-idle (fn [] (swap! state update-state)))]
#(rx/cancel! sem))))]
(let [initial (mf/use-memo initial-state)
state (mf/use-state initial)]
(mf/use-effect nil #(after-render state))
(for [item (:current @state)]
(children item)))))

View file

@ -13,11 +13,13 @@
[uxbox.util.dom :as dom] [uxbox.util.dom :as dom]
[uxbox.util.i18n :as i18n :refer [t tr]] [uxbox.util.i18n :as i18n :refer [t tr]]
[uxbox.util.router :as rt] [uxbox.util.router :as rt]
[uxbox.util.timers :as ts]
[uxbox.util.time :as dt])) [uxbox.util.time :as dt]))
;; --- Grid Item Thumbnail ;; --- Grid Item Thumbnail
(mf/defc grid-item-thumbnail (mf/defc grid-item-thumbnail
{::mf/wrap [#(mf/deferred % ts/schedule-on-idle)]}
[{:keys [file] :as props}] [{:keys [file] :as props}]
[:div.grid-item-th [:div.grid-item-th
[:& exports/page-svg {:data (:data file) [:& exports/page-svg {:data (:data file)

View file

@ -1,384 +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) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
;; Copyright (c) 2015-2019 Andrey Antukh <niwi@niwi.nz>
(ns uxbox.main.ui.dashboard.icons
(:require
[cljs.spec.alpha :as s]
[cuerdas.core :as str]
[lentes.core :as l]
[rumext.alpha :as mf]
[uxbox.common.data :as d]
[uxbox.common.spec :as us]
[uxbox.builtins.icons :as i]
[uxbox.main.data.icons :as di]
[uxbox.main.store :as st]
[uxbox.main.ui.confirm :refer [confirm-dialog]]
[uxbox.main.ui.dashboard.common :as common]
[uxbox.main.ui.keyboard :as kbd]
[uxbox.main.ui.modal :as modal]
[uxbox.main.ui.shapes.icon :as icon]
[uxbox.util.components :refer [chunked-list]]
[uxbox.util.data :refer [read-string seek]]
[uxbox.util.dom :as dom]
[uxbox.util.i18n :as i18n :refer [tr t]]
[uxbox.util.router :as rt]
[uxbox.util.time :as dt]))
;; ;; --- Helpers & Constants
;;
;; (def +ordering-options+
;; {:name "ds.ordering.by-name"
;; :created "ds.ordering.by-creation-date"})
;;
;; (defn- sort-icons-by
;; [ordering icons]
;; (case ordering
;; :name (sort-by :name icons)
;; :created (reverse (sort-by :created-at icons))
;; icons))
;;
;; (defn- contains-term?
;; [phrase term]
;; {:pre [(string? phrase)
;; (string? term)]}
;; (let [term (name term)]
;; (str/includes? (str/lower phrase) (str/trim (str/lower term)))))
;;
;; (defn- filter-icons-by
;; [term icons]
;; (if (str/blank? term)
;; icons
;; (filter #(contains-term? (:name %) term) icons)))
;;
;; ;; --- Component: Grid Header
;;
;; (mf/defc grid-header
;; [{:keys [collection] :as props}]
;; (let [{:keys [id type]} collection
;; on-change #(st/emit! (di/rename-collection id %))
;; on-deleted #(st/emit! (rt/nav :dashboard-icons nil {:type type}))
;; delete #(st/emit! (di/delete-collection id on-deleted))
;; on-delete #(modal/show! confirm-dialog {:on-accept delete})]
;; [:& common/grid-header {:value (:name collection)
;; :on-change on-change
;; :on-delete on-delete}]))
;;
;; ;; --- Nav
;;
;; (mf/defc nav-item
;; [{:keys [collection selected?] :as props}]
;; (let [local (mf/use-state {})
;; {:keys [id type name]} collection
;; editable? (= type :own)
;;
;; on-click
;; (fn [event]
;; (let [type (or type :own)]
;; (st/emit! (rt/nav :dashboard-icons {} {:type type :id id}))))
;;
;;
;; on-input-change
;; (fn [event]
;; (-> (dom/get-target event)
;; (dom/get-value)
;; (swap! local assoc :name)))
;;
;; on-cancel #(swap! local dissoc :name :edit)
;; on-double-click #(when editable? (swap! local assoc :edit true))
;;
;; on-input-keyup
;; (fn [event]
;; (when (kbd/enter? event)
;; (let [value (-> (dom/get-target event) (dom/get-value))]
;; (st/emit! (di/rename-collection id (str/trim (:name @local))))
;; (swap! local assoc :edit false))))]
;;
;; [:li {:on-click on-click
;; :on-double-click on-double-click
;; :class-name (when selected? "current")}
;; (if (:edit @local)
;; [:div
;; [:input.element-title {:value (or (:name @local) name)
;; :on-change on-input-change
;; :on-key-down on-input-keyup}]
;; [:span.close {:on-click on-cancel} i/close]]
;; [:span.element-title name])]))
;;
;;
;; (mf/defc nav
;; [{:keys [id type collections] :as props}]
;; (let [locale (i18n/use-locale)
;; own? (= type :own)
;; builtin? (= type :builtin)
;; create-collection #(st/emit! di/create-collection)
;; select-own-tab #(st/emit! (rt/nav :dashboard-icons nil {:type :own}))
;; select-buitin-tab #(st/emit! (rt/nav :dashboard-icons nil {:type :builtin}))]
;;
;; [:div.library-bar
;; [:div.library-bar-inside
;; ;; Tabs
;; [:ul.library-tabs
;; [:li {:class (when own? "current")
;; :on-click select-own-tab}
;; (t locale "ds.your-icons-title")]
;;
;; [:li {:class (when builtin? "current")
;; :on-click select-buitin-tab}
;; (t locale "ds.store-icons-title")]]
;;
;;
;; ;; Collections List
;; [:ul.library-elements
;; (when own?
;; [:li
;; [:a.btn-primary {:on-click #(st/emit! di/create-collection)}
;; (tr "ds.icons-collection.new")]])
;; (for [item collections]
;; [:& nav-item {:collection item
;; :selected? (= (:id item) id)
;; :key (:id item)}])]]]))
;;
;;
;; ;; (mf/def grid-options-tooltip
;; ;; :mixins [mf/reactive mf/memo]
;;
;; ;; :render
;; ;; (fn [own {:keys [selected on-select title]}]
;; ;; {:pre [(uuid? selected)
;; ;; (fn? on-select)
;; ;; (string? title)]}
;; ;; (let [colls (mf/react collections-iref)
;; ;; colls (->> (vals colls)
;; ;; (filter #(= :own (:type %)))
;; ;; (remove #(= selected (:id %)))
;; ;; (sort-by :name colls))
;; ;; on-select (fn [event id]
;; ;; (dom/prevent-default event)
;; ;; (dom/stop-propagation event)
;; ;; (on-select id))]
;; ;; [:ul.move-list
;; ;; [:li.title title]
;; ;; [:li
;; ;; [:a {:href "#" :on-click #(on-select % nil)} "Storage"]]
;; ;; (for [{:keys [id name] :as coll} colls]
;; ;; [:li {:key (pr-str id)}
;; ;; [:a {:on-click #(on-select % id)} name]])])))
;;
;; (mf/defc grid-options
;; [{:keys [id type selected] :as props}]
;; (let [local (mf/use-state {})
;; delete #(st/emit! di/delete-selected)
;; on-delete #(modal/show! confirm-dialog {:on-accept delete})
;;
;; ;; (on-toggle-copy [event]
;; ;; (swap! local update :show-copy-tooltip not))
;; ;; (on-toggle-move [event]
;; ;; (swap! local update :show-move-tooltip not))
;; ;; (on-copy [selected]
;; ;; (swap! local assoc
;; ;; :show-move-tooltip false
;; ;; :show-copy-tooltip false)
;; ;; (st/emit! (di/copy-selected selected)))
;; ;; (on-move [selected]
;; ;; (swap! local assoc
;; ;; :show-move-tooltip false
;; ;; :show-copy-tooltip false)
;; ;; (st/emit! (di/move-selected selected)))
;; ;; (on-rename [event]
;; ;; (let [selected (first selected)]
;; ;; (st/emit! (di/update-opts :edition selected))))
;; ]
;; ;; MULTISELECT OPTIONS BAR
;; [:div.multiselect-bar
;; (when (= type :own)
;; ;; If editable
;; [:div.multiselect-nav
;; ;; [:span.move-item.tooltip.tooltip-top
;; ;; {:alt (tr "ds.multiselect-bar.copy")
;; ;; :on-click on-toggle-copy}
;; ;; (when (:show-copy-tooltip @local)
;; ;; [:& grid-options-tooltip {:selected id
;; ;; :title (tr "ds.multiselect-bar.copy-to-library")
;; ;; :on-select on-copy}])
;; ;; i/copy]
;; ;; [:span.move-item.tooltip.tooltip-top
;; ;; {:alt (tr "ds.multiselect-bar.move")
;; ;; :on-click on-toggle-move}
;; ;; (when (:show-move-tooltip @local)
;; ;; [:& grid-options-tooltip {:selected id
;; ;; :title (tr "ds.multiselect-bar.move-to-library")
;; ;; :on-select on-move}])
;; ;; i/move]
;; ;; (when (= 1 (count selected))
;; ;; [:span.move-item.tooltip.tooltip-top {:alt (tr "ds.multiselect-bar.rename")
;; ;; :on-click on-rename}
;; ;; i/pencil])
;; [:span.delete.tooltip.tooltip-top
;; {:alt (tr "ds.multiselect-bar.delete")
;; :on-click on-delete}
;; i/trash]]
;;
;; ;; If not editable
;; ;; [:div.multiselect-nav
;; ;; [:span.move-item.tooltip.tooltip-top {:alt (tr "ds.multiselect-bar.copy")
;; ;; :on-click on-toggle-copy}
;; ;; (when (:show-copy-tooltip @local)
;; ;; [:& grid-options-tooltip {:selected id
;; ;; :title (tr "ds.multiselect-bar.copy-to-library")
;; ;; :on-select on-copy}])
;; ;; i/organize]]
;; )]))
;;
;; ;; --- Grid Form
;;
;; (mf/defc grid-form
;; [{:keys [id type uploading?] :as props}]
;; (let [locale (i18n/use-locale)
;; input (mf/use-ref nil)
;; on-click #(dom/click (mf/ref-node input))
;; on-select #(st/emit! (->> (dom/get-target %)
;; (dom/get-files)
;; (array-seq)
;; (di/create-icons id)))]
;; [:div.grid-item.add-project {:on-click on-click}
;; (if uploading?
;; [:div i/loader-pencil]
;; [:span (t locale "ds.icon-new")])
;; [:input.upload-icon-input
;; {:style {:display "none"}
;; :multiple true
;; :ref input
;; :value ""
;; :accept "icon/svg+xml"
;; :type "file"
;; :on-change on-select}]]))
;;
;; ;; --- Grid Item
;;
;; (mf/defc grid-item
;; [{:keys [icon selected? edition?] :as props}]
;; (let [toggle-selection #(st/emit! (if selected?
;; (di/deselect-icon (:id icon))
;; (di/select-icon (:id icon))))
;; on-blur
;; (fn [event]
;; (let [target (dom/get-target event)
;; name (dom/get-value target)]
;; (st/emit! (di/update-opts :edition false)
;; (di/rename-icon (:id icon) name))))
;;
;; on-key-down
;; (fn [event]
;; (when (kbd/enter? event)
;; (on-blur event)))
;;
;; ignore-click
;; (fn [event]
;; (dom/stop-propagation event)
;; (dom/prevent-default event))
;;
;; on-edit
;; (fn [event]
;; (dom/stop-propagation event)
;; (dom/prevent-default event)
;; (st/emit! (di/update-opts :edition (:id icon))))]
;;
;; [:div.grid-item.small-item.project-th
;; [:div.input-checkbox.check-primary
;; [:input {:type "checkbox"
;; :id (:id icon)
;; :on-change toggle-selection
;; :checked selected?}]
;; [:label {:for (:id icon)}]]
;; [:span.grid-item-icon
;; [:& icon/icon-svg {:shape icon}]]
;; [:div.item-info {:on-click ignore-click}
;; (if edition?
;; [:input.element-name {:type "text"
;; :auto-focus true
;; :on-key-down on-key-down
;; :on-blur on-blur
;; :on-click on-edit
;; :default-value (:name icon)}]
;; [:h3 {:on-double-click on-edit}
;; (:name icon)])
;; (str (tr "ds.uploaded-at" (dt/format (:created-at icon) "dd/MM/yyyy")))]]))
;;
;; ;; --- Grid
;;
;; (def icons-iref
;; (-> (comp (l/key :icons) (l/lens vals))
;; (l/derive st/state)))
;;
;; (mf/defc grid
;; [{:keys [id type collection opts] :as props}]
;; (let [editable? (= type :own)
;; icons (->> (mf/deref icons-iref)
;; (filter-icons-by (:filter opts ""))
;; (sort-icons-by (:order opts :name)))]
;; [:div.dashboard-grid-content
;; [:div.dashboard-grid-row
;; (when editable?
;; [:& grid-form {:id id :type type :uploading? (:uploading opts)}])
;;
;; [:& chunked-list {:items icons
;; :initial-size 30
;; :chunk-size 30
;; :key (str type id (count icons))}
;; (fn [icon]
;; [:& grid-item {:icon icon
;; :key (:id icon)
;; :selected (contains? (:selected opts) (:id icon))
;; :edition? (= (:edition opts) (:id icon))}])]]]))
;;
;; ;; --- Content
;;
;; (def opts-iref
;; (-> (l/key :dashboard-icons)
;; (l/derive st/state)))
;;
;; (mf/defc content
;; [{:keys [id type collection] :as props}]
;; (let [{:keys [selected] :as opts} (mf/deref opts-iref)]
;; [:section.dashboard-grid.library
;; (when collection
;; [:& grid-header {:collection collection}])
;; (if collection
;; [:& grid {:id id :type type :collection collection :opts opts}]
;; [:span "EMPTY STATE TODO"])
;; (when-not (empty? selected)
;; #_[:& grid-options {:id id :type type :selected (:selected opts)}])]))
;;
;; ;; --- Icons Page
;;
;; (def collections-iref
;; (-> (l/key :icons-collections)
;; (l/derive st/state)))
;;
;; (mf/defc icons-page
;; [{:keys [id type] :as props}]
;; (let [type (or type :own)
;; collections (mf/deref collections-iref)
;; collections (cond->> (vals collections)
;; (= type :own) (filter #(= :own (:type %)))
;; (= type :builtin) (filter #(= :builtin (:type %)))
;; true (sort-by :created-at))
;;
;; collection (cond
;; (uuid? id) (seek #(= id (:id %)) collections)
;; :else (first collections))
;;
;; id (:id collection)]
;;
;; (mf/use-effect #(st/emit! di/fetch-collections))
;; (mf/use-effect
;; {:fn #(when id (st/emit! (di/initialize id)))
;; :deps (mf/deps id)})
;;
;; [:section.dashboard-content
;; [:& nav {:type type :id id :collections collections}]
;; [:& content {:type type :id id :collection collection}]]))

View file

@ -377,16 +377,6 @@
) )
}] }]
[:* [:*
;; TODO: Fix the chunked list
#_[:& chunked-list {:items items
:initial-size 30
:chunk-size 30}
(fn [item]
(let [item (assoc item :key (:id item))]
(case section
:icons [:& library-icon-card item]
:images [:& library-image-card item]
:palettes [:& library-color-card item ])))]
(if (> (count items) 0) (if (> (count items) 0)
[:div.library-page-cards-container [:div.library-page-cards-container
(for [item items] (for [item items]

View file

@ -167,7 +167,7 @@
:type "text" :type "text"
:placeholder (t locale "ds.search.placeholder") :placeholder (t locale "ds.search.placeholder")
:default-value search-term-not-nil :default-value search-term-not-nil
:autoComplete "off" :auto-complete "off"
:on-focus on-search-focus :on-focus on-search-focus
:on-change on-search-change :on-change on-search-change
:ref #(when % (set! (.-value %) search-term-not-nil))}] :ref #(when % (set! (.-value %) search-term-not-nil))}]

View file

@ -20,6 +20,7 @@
[uxbox.main.ui.shapes.attrs :as attrs] [uxbox.main.ui.shapes.attrs :as attrs]
[uxbox.main.ui.shapes.common :as common] [uxbox.main.ui.shapes.common :as common]
[uxbox.util.dom :as dom] [uxbox.util.dom :as dom]
[uxbox.util.timers :as ts]
[uxbox.util.interop :as itr] [uxbox.util.interop :as itr]
[uxbox.util.geom.matrix :as gmt] [uxbox.util.geom.matrix :as gmt]
[uxbox.util.geom.point :as gpt])) [uxbox.util.geom.point :as gpt]))
@ -53,7 +54,8 @@
[shape-wrapper] [shape-wrapper]
(let [frame-shape (frame-shape shape-wrapper)] (let [frame-shape (frame-shape shape-wrapper)]
(mf/fnc frame-wrapper (mf/fnc frame-wrapper
{::mf/wrap [#(mf/memo' % frame-wrapper-memo-equals?)] {::mf/wrap [#(mf/memo' % frame-wrapper-memo-equals?)
#(mf/deferred % ts/schedule-on-idle)]
::mf/wrap-props false} ::mf/wrap-props false}
[props] [props]
(let [shape (unchecked-get props "shape") (let [shape (unchecked-get props "shape")

View file

@ -19,6 +19,7 @@
[uxbox.main.store :as st] [uxbox.main.store :as st]
[uxbox.main.streams :as ms] [uxbox.main.streams :as ms]
[uxbox.util.dom :as dom] [uxbox.util.dom :as dom]
[uxbox.util.object :as obj]
[uxbox.util.geom.point :as gpt] [uxbox.util.geom.point :as gpt]
[uxbox.util.geom.matrix :as gmt] [uxbox.util.geom.matrix :as gmt]
[uxbox.util.debug :refer [debug?]])) [uxbox.util.debug :refer [debug?]]))
@ -32,49 +33,74 @@
60) 60)
(mf/defc control-item (mf/defc control-item
[{:keys [class on-click r cy cx] :as props}] {::mf/wrap-props false}
[:circle [props]
{:class-name class (let [class (obj/get props "class")
:on-mouse-down on-click on-click (obj/get props "on-click")
:r r r (obj/get props "r")
:style {:fillOpacity "1" cx (obj/get props "cx")
:strokeWidth "1px" cy (obj/get props "cy")]
[:circle
{:class-name class
:on-mouse-down on-click
:r r
:style {:fillOpacity "1"
:strokeWidth "1px"
:vectorEffect "non-scaling-stroke"} :vectorEffect "non-scaling-stroke"}
:fill "#ffffff" :fill "#ffffff"
:stroke "#1FDEA7" :stroke "#1FDEA7"
:cx cx :cx cx
:cy cy}]) :cy cy}]))
(def ^:private rotate-cursor-svg "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20px' height='20px' transform='rotate(%s)' viewBox='0 0 132.292 132.006'%3E%3Cpath d='M85.225 3.48c.034 4.989-.093 9.852-.533 14.78-29.218 5.971-54.975 27.9-63.682 56.683-1.51 2.923-1.431 7.632-3.617 9.546-5.825.472-11.544.5-17.393.45 11.047 15.332 20.241 32.328 32.296 46.725 5.632 1.855 7.155-5.529 10.066-8.533 8.12-12.425 17.252-24.318 24.269-37.482-6.25-.86-12.564-.88-18.857-1.057 5.068-17.605 19.763-31.81 37.091-37.122.181 6.402.206 12.825 1.065 19.184 15.838-9.05 30.899-19.617 45.601-30.257 2.985-4.77-3.574-7.681-6.592-9.791C111.753 17.676 98.475 8.889 85.23.046l-.005 3.435z'/%3E%3Cpath fill='%23fff' d='M92.478 23.995s-1.143.906-6.714 1.923c-29.356 5.924-54.352 30.23-59.717 59.973-.605 3.728-1.09 5.49-1.09 5.49l-11.483-.002s7.84 10.845 10.438 15.486c3.333 4.988 6.674 9.971 10.076 14.912a2266.92 2266.92 0 0019.723-29.326c-5.175-.16-10.35-.343-15.522-.572 3.584-27.315 26.742-50.186 53.91-54.096.306 5.297.472 10.628.631 15.91a2206.462 2206.462 0 0029.333-19.726c-9.75-6.7-19.63-13.524-29.483-20.12z'/%3E%3C/svg%3E\") 10 10, auto") (def ^:private rotate-cursor-svg "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20px' height='20px' transform='rotate(%s)' viewBox='0 0 132.292 132.006'%3E%3Cpath d='M85.225 3.48c.034 4.989-.093 9.852-.533 14.78-29.218 5.971-54.975 27.9-63.682 56.683-1.51 2.923-1.431 7.632-3.617 9.546-5.825.472-11.544.5-17.393.45 11.047 15.332 20.241 32.328 32.296 46.725 5.632 1.855 7.155-5.529 10.066-8.533 8.12-12.425 17.252-24.318 24.269-37.482-6.25-.86-12.564-.88-18.857-1.057 5.068-17.605 19.763-31.81 37.091-37.122.181 6.402.206 12.825 1.065 19.184 15.838-9.05 30.899-19.617 45.601-30.257 2.985-4.77-3.574-7.681-6.592-9.791C111.753 17.676 98.475 8.889 85.23.046l-.005 3.435z'/%3E%3Cpath fill='%23fff' d='M92.478 23.995s-1.143.906-6.714 1.923c-29.356 5.924-54.352 30.23-59.717 59.973-.605 3.728-1.09 5.49-1.09 5.49l-11.483-.002s7.84 10.845 10.438 15.486c3.333 4.988 6.674 9.971 10.076 14.912a2266.92 2266.92 0 0019.723-29.326c-5.175-.16-10.35-.343-15.522-.572 3.584-27.315 26.742-50.186 53.91-54.096.306 5.297.472 10.628.631 15.91a2206.462 2206.462 0 0029.333-19.726c-9.75-6.7-19.63-13.524-29.483-20.12z'/%3E%3C/svg%3E\") 10 10, auto")
(defn rotation-cursor
[angle]
(str "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20px' height='20px' transform='rotate(" angle ")' viewBox='0 0 132.292 132.006'%3E%3Cpath d='M85.225 3.48c.034 4.989-.093 9.852-.533 14.78-29.218 5.971-54.975 27.9-63.682 56.683-1.51 2.923-1.431 7.632-3.617 9.546-5.825.472-11.544.5-17.393.45 11.047 15.332 20.241 32.328 32.296 46.725 5.632 1.855 7.155-5.529 10.066-8.533 8.12-12.425 17.252-24.318 24.269-37.482-6.25-.86-12.564-.88-18.857-1.057 5.068-17.605 19.763-31.81 37.091-37.122.181 6.402.206 12.825 1.065 19.184 15.838-9.05 30.899-19.617 45.601-30.257 2.985-4.77-3.574-7.681-6.592-9.791C111.753 17.676 98.475 8.889 85.23.046l-.005 3.435z'/%3E%3Cpath fill='%23fff' d='M92.478 23.995s-1.143.906-6.714 1.923c-29.356 5.924-54.352 30.23-59.717 59.973-.605 3.728-1.09 5.49-1.09 5.49l-11.483-.002s7.84 10.845 10.438 15.486c3.333 4.988 6.674 9.971 10.076 14.912a2266.92 2266.92 0 0019.723-29.326c-5.175-.16-10.35-.343-15.522-.572 3.584-27.315 26.742-50.186 53.91-54.096.306 5.297.472 10.628.631 15.91a2206.462 2206.462 0 0029.333-19.726c-9.75-6.7-19.63-13.524-29.483-20.12z'/%3E%3C/svg%3E\") 10 10, auto"))
(def rotation-handler-positions
#{:top-left :top-right :bottom-left :bottom-right})
(mf/defc rotation-handler (mf/defc rotation-handler
[{:keys [cx cy position on-mouse-down rotation zoom]}] {::mf/wrap-props false}
(when (#{:top-left :top-right :bottom-left :bottom-right} position) [props]
(let [size (/ 20 zoom) (let [cx (obj/get props "cx")
rotation (or rotation 0) cy (obj/get props "cy")
x (- cx (if (#{:top-left :bottom-left} position) size 0)) position (obj/get props "position")
y (- cy (if (#{:top-left :top-right} position) size 0)) on-mouse-down (obj/get props "on-mouse-down")
angle (case position rotation (obj/get props "rotation")
:top-left 0 zoom (obj/get props "zoom")]
:top-right 90 (when (contains? rotation-handler-positions position)
:bottom-right 180 (let [size (/ 20 zoom)
:bottom-left 270)] rotation (or rotation 0)
[:rect {:style {:cursor (str/format rotate-cursor-svg (str (+ rotation angle)))} x (- cx (if (#{:top-left :bottom-left} position) size 0))
:x x y (- cy (if (#{:top-left :top-right} position) size 0))
:y y angle (case position
:width size :top-left 0
:height size :top-right 90
:fill (if (debug? :rotation-handler) "red" "transparent") :bottom-right 180
:transform (gmt/rotate-matrix rotation (gpt/point cx cy)) :bottom-left 270)]
:on-mouse-down (or on-mouse-down (fn []))}]))) [:rect {:style {:cursor (rotation-cursor (+ rotation angle))}
:x x
:y y
:width size
:height size
:fill (if (debug? :rotation-handler) "red" "transparent")
:transform (gmt/rotate-matrix rotation (gpt/point cx cy))
:on-mouse-down (or on-mouse-down (fn []))}]))))
(mf/defc controls (mf/defc controls
[{:keys [shape zoom on-resize on-rotate] :as props}] {::mf/wrap-props false}
(let [{:keys [x y width height rotation] :as shape} (geom/shape->rect-shape shape) [props]
(let [shape (obj/get props "shape")
zoom (obj/get props "zoom")
on-resize (obj/get props "on-resize")
on-rotate (obj/get props "on-rotate")
{:keys [x y width height rotation] :as shape} (geom/shape->rect-shape shape)
radius (if (> (max width height) handler-size-threshold) 4.0 4.0) radius (if (> (max width height) handler-size-threshold) 4.0 4.0)
transform (geom/transform-matrix shape) transform (geom/transform-matrix shape)
resize-handlers {:top [(+ x (/ width 2 )) y] resize-handlers {:top [(+ x (/ width 2 )) y]
:right [(+ x width) (+ y (/ height 2))] :right [(+ x width) (+ y (/ height 2))]
:bottom [(+ x (/ width 2)) (+ y height)] :bottom [(+ x (/ width 2)) (+ y height)]
@ -83,7 +109,7 @@
:top-right [(+ x width) y] :top-right [(+ x width) y]
:bottom-left [x (+ y height)] :bottom-left [x (+ y height)]
:bottom-right [(+ x width) (+ y height)]}] :bottom-right [(+ x width) (+ y height)]}]
[:g.controls [:g.controls
[:rect.main {:transform transform [:rect.main {:transform transform
:x (- x 1) :y (- y 1) :x (- x 1) :y (- y 1)
@ -95,17 +121,15 @@
(for [[position [cx cy]] resize-handlers] (for [[position [cx cy]] resize-handlers]
(let [tp (gpt/transform (gpt/point cx cy) transform)] (let [tp (gpt/transform (gpt/point cx cy) transform)]
[:* {:key (str "fragment-" (name position))} [:* {:key (name position)}
[:& rotation-handler {:key (str "rotation-" (name position)) [:& rotation-handler {:cx (:x tp)
:cx (:x tp)
:cy (:y tp) :cy (:y tp)
:position position :position position
:rotation (:rotation shape) :rotation (:rotation shape)
:zoom zoom :zoom zoom
:on-mouse-down on-rotate}] :on-mouse-down on-rotate}]
[:& control-item {:key (str "resize-" (name position)) [:& control-item {:class (name position)
:class (name position)
:on-click #(on-resize position %) :on-click #(on-resize position %)
:r (/ radius zoom) :r (/ radius zoom)
:cx (:x tp) :cx (:x tp)
@ -170,6 +194,7 @@
[{:keys [shapes selected zoom objects] :as props}] [{:keys [shapes selected zoom objects] :as props}]
(let [shape (geom/selection-rect shapes) (let [shape (geom/selection-rect shapes)
shape-center (geom/center shape) shape-center (geom/center shape)
on-resize #(do (dom/stop-propagation %2) on-resize #(do (dom/stop-propagation %2)
(st/emit! (dw/start-resize %1 selected shape objects))) (st/emit! (dw/start-resize %1 selected shape objects)))
@ -189,11 +214,14 @@
(let [shape-id (:id shape) (let [shape-id (:id shape)
shape (geom/transform-shape shape) shape (geom/transform-shape shape)
shape' (if (debug? :simple-selection) (geom/selection-rect [shape]) shape) shape' (if (debug? :simple-selection) (geom/selection-rect [shape]) shape)
on-resize #(do (dom/stop-propagation %2)
(st/emit! (dw/start-resize %1 #{shape-id} shape' objects)))
on-rotate #(do (dom/stop-propagation %)
(st/emit! (dw/start-rotate [shape])))]
on-resize
#(do (dom/stop-propagation %2)
(st/emit! (dw/start-resize %1 #{shape-id} shape' objects)))
on-rotate
#(do (dom/stop-propagation %)
(st/emit! (dw/start-rotate [shape])))]
[:& controls {:shape shape' [:& controls {:shape shape'
:zoom zoom :zoom zoom
:on-rotate on-rotate :on-rotate on-rotate

View file

@ -10,21 +10,23 @@
(ns uxbox.main.ui.workspace.sidebar.layers (ns uxbox.main.ui.workspace.sidebar.layers
(:require (:require
[rumext.alpha :as mf]
[okulary.core :as l] [okulary.core :as l]
[uxbox.common.data :as d] [rumext.alpha :as mf]
[uxbox.builtins.icons :as i] [uxbox.builtins.icons :as i]
[uxbox.main.data.workspace :as dw] [uxbox.common.data :as d]
[uxbox.common.uuid :as uuid]
[uxbox.main.data.helpers :as dh] [uxbox.main.data.helpers :as dh]
[uxbox.main.data.workspace :as dw]
[uxbox.main.refs :as refs] [uxbox.main.refs :as refs]
[uxbox.main.store :as st] [uxbox.main.store :as st]
[uxbox.main.ui.hooks :as hooks] [uxbox.main.ui.hooks :as hooks]
[uxbox.main.ui.keyboard :as kbd] [uxbox.main.ui.keyboard :as kbd]
[uxbox.main.ui.shapes.icon :as icon] [uxbox.main.ui.shapes.icon :as icon]
[uxbox.util.object :as obj]
[uxbox.util.dom :as dom] [uxbox.util.dom :as dom]
[uxbox.util.perf :as perf] [uxbox.util.timers :as ts]
[uxbox.common.uuid :as uuid] [uxbox.util.i18n :as i18n :refer [t]]
[uxbox.util.i18n :as i18n :refer [t]])) [uxbox.util.perf :as perf]))
;; --- Helpers ;; --- Helpers
@ -230,28 +232,16 @@
(mf/defc frame-wrapper (mf/defc frame-wrapper
{::mf/wrap-props false {::mf/wrap-props false
::mf/wrap [#(mf/memo' % frame-wrapper-memo-equals?)]} ::mf/wrap [#(mf/memo' % frame-wrapper-memo-equals?)
#(mf/deferred % ts/idle-then-raf)]}
[props] [props]
[:> layer-item props]) [:> layer-item props])
(def ^:private layers-objects
(letfn [(strip-data [obj]
(select-keys obj [:id :name :blocked :hidden :shapes :type]))
(selector [{:keys [objects] :as data}]
(persistent!
(reduce-kv (fn [res id obj]
(assoc! res id (strip-data obj)))
(transient {})
objects)))]
(l/derived selector refs/workspace-data =)))
(mf/defc layers-tree (mf/defc layers-tree
{::mf/wrap [mf/memo]} {::mf/wrap [#(mf/memo % =)]}
[] [{:keys [objects] :as props}]
(let [selected (mf/deref refs/selected-shapes) (let [selected (mf/deref refs/selected-shapes)
objects (mf/deref layers-objects)
root (get objects uuid/zero)] root (get objects uuid/zero)]
;; [:& perf/profiler {:label "layers-tree" :enabled false}
[:ul.element-list [:ul.element-list
(for [[index id] (reverse (d/enumerate (:shapes root)))] (for [[index id] (reverse (d/enumerate (:shapes root)))]
(let [obj (get objects id)] (let [obj (get objects id)]
@ -269,16 +259,30 @@
:objects objects :objects objects
:key id}])))])) :key id}])))]))
(defn- strip-objects
[objects]
(let [strip-data #(select-keys % [:id :name :blocked :hidden :shapes :type])]
(persistent!
(reduce-kv (fn [res id obj]
(assoc! res id (strip-data obj)))
(transient {})
objects))))
(mf/defc layers-tree-wrapper
{::mf/wrap-props false
::mf/wrap [mf/memo #(mf/throttle % 200)]}
[props]
(let [objects (obj/get props "objects")
objects (strip-objects objects)]
[:& layers-tree {:objects objects}]))
;; --- Layers Toolbox ;; --- Layers Toolbox
;; NOTE: we need to consider using something like react window for
;; only render visible items instead of all.
(mf/defc layers-toolbox (mf/defc layers-toolbox
{:wrap [mf/memo]} {:wrap [mf/memo]}
[{:keys [page] :as props}] [{:keys [page] :as props}]
(let [locale (i18n/use-locale) (let [locale (mf/deref i18n/locale)
data (mf/deref refs/workspace-data)
on-click #(st/emit! (dw/toggle-layout-flag :layers))] on-click #(st/emit! (dw/toggle-layout-flag :layers))]
[:div#layers.tool-window [:div#layers.tool-window
[:div.tool-window-bar [:div.tool-window-bar
@ -286,4 +290,5 @@
[:span (:name page)] [:span (:name page)]
#_[:div.tool-window-close {:on-click on-click} i/close]] #_[:div.tool-window-close {:on-click on-click} i/close]]
[:div.tool-window-content [:div.tool-window-content
[:& layers-tree {:key (:id page)}]]])) [:& layers-tree-wrapper {:key (:id page)
:objects (:objects data)}]]]))

View file

@ -30,6 +30,7 @@
;; --- Options ;; --- Options
(mf/defc shape-options (mf/defc shape-options
{::mf/wrap [#(mf/throttle % 60)]}
[{:keys [shape] :as props}] [{:keys [shape] :as props}]
[:div [:div
(case (:type shape) (case (:type shape)

View file

@ -17,10 +17,21 @@
[uxbox.main.ui.modal :as modal] [uxbox.main.ui.modal :as modal]
[uxbox.main.ui.workspace.colorpicker :refer [colorpicker-modal]] [uxbox.main.ui.workspace.colorpicker :refer [colorpicker-modal]]
[uxbox.util.dom :as dom] [uxbox.util.dom :as dom]
[uxbox.util.object :as obj]
[uxbox.util.math :as math] [uxbox.util.math :as math]
[uxbox.util.i18n :as i18n :refer [tr t]])) [uxbox.util.i18n :as i18n :refer [tr t]]))
(defn- fill-menu-memo-equals?
[np op]
(let [new-shape (obj/get np "shape")
old-shape (obj/get op "shape")]
(and (identical? (:fill-color new-shape)
(:fill-color old-shape))
(identical? (:fill-opacity new-shape)
(:fill-opacity old-shape)))))
(mf/defc fill-menu (mf/defc fill-menu
{::mf/wrap [#(mf/memo' % fill-menu-memo-equals?)]}
[{:keys [shape] :as props}] [{:keys [shape] :as props}]
(let [locale (i18n/use-locale) (let [locale (i18n/use-locale)

View file

@ -21,9 +21,10 @@
[uxbox.util.math :as math] [uxbox.util.math :as math]
[uxbox.util.i18n :refer [t] :as i18n])) [uxbox.util.i18n :refer [t] :as i18n]))
;; -- User/drawing coords ;; -- User/drawing coords
(defn user-coords-vector [shape]
(defn user-coords-vector
[shape]
(let [{sel-x :x sel-y :y :as selrect} (let [{sel-x :x sel-y :y :as selrect}
(-> shape (-> shape
gsh/shape->path gsh/shape->path
@ -35,11 +36,13 @@
dy (- rec-y sel-y)] dy (- rec-y sel-y)]
(gpt/point dx dy))) (gpt/point dx dy)))
(defn user->draw [{:keys [x y width height] :as shape}] (defn user->draw
[{:keys [x y width height] :as shape}]
(let [dv (user-coords-vector shape)] (let [dv (user-coords-vector shape)]
(-> shape (gsh/move dv)))) (-> shape (gsh/move dv))))
(defn draw->user [{:keys [x y width height] :as shape}] (defn draw->user
[{:keys [x y width height] :as shape}]
(let [dv (user-coords-vector shape)] (let [dv (user-coords-vector shape)]
(-> shape (gsh/move (gpt/negate dv))))) (-> shape (gsh/move (gpt/negate dv)))))

View file

@ -15,6 +15,7 @@
[uxbox.main.ui.workspace.sidebar.options.stroke :refer [stroke-menu]])) [uxbox.main.ui.workspace.sidebar.options.stroke :refer [stroke-menu]]))
(mf/defc options (mf/defc options
{::mf/wrap [mf/memo]}
[{:keys [shape] :as props}] [{:keys [shape] :as props}]
[:div [:div
[:& measures-menu {:shape shape}] [:& measures-menu {:shape shape}]

View file

@ -17,10 +17,27 @@
[uxbox.main.ui.modal :as modal] [uxbox.main.ui.modal :as modal]
[uxbox.main.ui.workspace.colorpicker :refer [colorpicker-modal]] [uxbox.main.ui.workspace.colorpicker :refer [colorpicker-modal]]
[uxbox.util.dom :as dom] [uxbox.util.dom :as dom]
[uxbox.util.object :as obj]
[uxbox.util.i18n :as i18n :refer [tr t]] [uxbox.util.i18n :as i18n :refer [tr t]]
[uxbox.util.math :as math])) [uxbox.util.math :as math]))
(defn- stroke-menu-memo-equals?
[np op]
(let [new-shape (obj/get np "shape")
old-shape (obj/get op "shape")]
(and (identical? (:stroke-style new-shape)
(:stroke-style old-shape))
(identical? (:stroke-alignment new-shape)
(:stroke-alignment old-shape))
(identical? (:stroke-width new-shape)
(:stroke-width old-shape))
(identical? (:stroke-color new-shape)
(:stroke-color old-shape))
(identical? (:stroke-opacity new-shape)
(:stroke-opacity old-shape)))))
(mf/defc stroke-menu (mf/defc stroke-menu
{::mf/wrap [#(mf/memo' % stroke-menu-memo-equals?)]}
[{:keys [shape] :as props}] [{:keys [shape] :as props}]
(let [locale (i18n/use-locale) (let [locale (i18n/use-locale)
show-options (not= (:stroke-style shape) :none) show-options (not= (:stroke-style shape) :none)

View file

@ -102,6 +102,7 @@
(-> (rx/take-until stoper ms/mouse-position) (-> (rx/take-until stoper ms/mouse-position)
(rx/subscribe #(on-point dom reference %)))))))) (rx/subscribe #(on-point dom reference %))))))))
;; --- Viewport ;; --- Viewport
(declare remote-user-cursors) (declare remote-user-cursors)

View file

@ -0,0 +1,53 @@
/**
* 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);
}
self.replace_token_BANG_ = function(instance, token) {
instance.replaceToken(token);
}
});

View file

@ -1,43 +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.components
"A collection of general purpose utility components."
(:require
[beicon.core :as rx]
[rumext.alpha :as mf]
[uxbox.util.timers :refer [schedule-on-idle]]))
;; TODO: this file is DEPRECATED (pending deletion)
(mf/defc chunked-list
[{:keys [items children initial-size chunk-size]
:or {initial-size 30 chunk-size 5}
:as props}]
(letfn [(initial-state []
(let [total (count items)
size (if (> total initial-size) initial-size total)
current (take size items)
pending (drop size items)]
{:current (vec current)
:pending pending
:pending-num (- total size)}))
(update-state [{:keys [current pending pending-num] :as state}]
(let [chunk-size (if (> pending-num chunk-size) chunk-size pending-num)]
{:current (into current (take chunk-size pending))
:pending (drop chunk-size pending)
:pending-num (- pending-num chunk-size)}))
(after-render [state]
(when (pos? (:pending-num @state))
(let [sem (schedule-on-idle (fn [] (swap! state update-state)))]
#(rx/cancel! sem))))]
(let [initial (mf/use-memo initial-state)
state (mf/use-state initial)]
(mf/use-effect nil #(after-render state))
(for [item (:current @state)]
(children item)))))

View file

@ -465,7 +465,8 @@
(declare transform-apply-modifiers) (declare transform-apply-modifiers)
(defn selection-rect-shape [shape] (defn selection-rect-shape
[shape]
(-> shape (-> shape
(transform-apply-modifiers) (transform-apply-modifiers)
(shape->rect-shape))) (shape->rect-shape)))

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."
@ -84,18 +87,54 @@
;; --- Navigate (Event) ;; --- Navigate (Event)
(deftype Navigate [id params qparams] (deftype Navigate [id params qparams replace]
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)]
(if ^boolean replace
(bhistory/replace-token! history path)
(bhistory/set-token! history path)))))
(defn nav (defn nav
([id] (nav id nil nil)) ([id] (nav id nil nil))
([id params] (nav id params nil)) ([id params] (nav id params nil))
([id params qparams] ([id params qparams] (Navigate. id params qparams false)))
{:pre [(keyword? id)]}
(Navigate. id params qparams))) (defn nav'
([id] (nav id nil nil))
([id params] (nav id params nil))
([id params qparams] (Navigate. id params qparams true)))
(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)]
(bhistory/enable! history)
(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/merge
(rx/of (on-change router (.getToken history)))
(->> (rx/create (fn [sink]
(let [key (e/listen history "navigate" #(sink (.-token %)))]
(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]))
@ -29,3 +32,12 @@
(reify rx/IDisposable (reify rx/IDisposable
(-dispose [_] (-dispose [_]
(js/cancelIdleCallback sem))))) (js/cancelIdleCallback sem)))))
(defn raf
[f]
(js/window.requestAnimationFrame f))
(defn idle-then-raf
[f]
(schedule-on-idle #(raf f)))