mirror of
https://github.com/penpot/penpot.git
synced 2025-07-29 21:27:33 +02:00
Merge pull request #193 from uxbox/other/fixes-and-performance-improvements
Other/fixes and performance improvements
This commit is contained in:
commit
37d0f20b7e
32 changed files with 600 additions and 1387 deletions
|
@ -89,7 +89,8 @@
|
|||
|
||||
(defn- retrieve-profile-by-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)
|
||||
|
|
|
@ -94,4 +94,4 @@
|
|||
(defn strip-private-attrs
|
||||
"Only selects a publicy visible profile attrs."
|
||||
[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]))
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
funcool/okulary {:mvn/version "2020.04.14-0"}
|
||||
funcool/potok {:mvn/version "2.8.0-SNAPSHOT"}
|
||||
funcool/promesa {:mvn/version "5.1.0"}
|
||||
funcool/rumext {:mvn/version "2020.04.14-1"}
|
||||
funcool/rumext {:mvn/version "2020.05.04-0"}
|
||||
}
|
||||
:aliases
|
||||
{:dev
|
||||
|
|
968
frontend/package-lock.json
generated
968
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -8,17 +8,17 @@
|
|||
;; Copyright (c) 2016-2020 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(ns uxbox.config
|
||||
(:require [goog.object :as gobj]))
|
||||
(:require [uxbox.util.object :as obj]))
|
||||
|
||||
(defn- get-current-origin
|
||||
[]
|
||||
(let [location (gobj/get goog.global "location")]
|
||||
(gobj/get location "origin")))
|
||||
(let [location (obj/get goog.global "location")]
|
||||
(obj/get location "origin")))
|
||||
|
||||
(let [config (gobj/get goog.global "uxboxConfig")
|
||||
public-url (gobj/get config "publicURL" "http://localhost:6060")]
|
||||
(let [config (obj/get goog.global "uxboxConfig")
|
||||
public-url (obj/get config "publicURL" "http://localhost:6060")]
|
||||
|
||||
(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 default-theme "default"))
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
[uxbox.main.ui.modal :refer [modal]]
|
||||
[uxbox.main.worker]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.html.history :as html-history]
|
||||
[uxbox.util.i18n :as i18n]
|
||||
[uxbox.util.theme :as theme]
|
||||
[uxbox.util.router :as rt]
|
||||
|
@ -29,38 +28,33 @@
|
|||
|
||||
(declare reinit)
|
||||
|
||||
(defn- on-navigate
|
||||
(defn on-navigate
|
||||
[router path]
|
||||
(let [match (rt/match router path)
|
||||
profile (:profile storage)]
|
||||
(cond
|
||||
(and (= path "") (not profile))
|
||||
(st/emit! (rt/nav :login))
|
||||
(rt/nav :login)
|
||||
|
||||
(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)
|
||||
(st/emit! (rt/nav :not-found))
|
||||
(rt/nav :not-found)
|
||||
|
||||
:else
|
||||
(st/emit! #(assoc % :route match)))))
|
||||
#(assoc % :route match))))
|
||||
|
||||
(defn init-ui
|
||||
[]
|
||||
(let [router (rt/init ui/routes)
|
||||
cpath (deref html-history/path)]
|
||||
(st/emit! (rt/initialize-router ui/routes)
|
||||
(rt/initialize-history on-navigate))
|
||||
|
||||
(st/emit! #(assoc % :router router))
|
||||
(add-watch html-history/path ::main #(on-navigate router %4))
|
||||
(when (:profile storage)
|
||||
(st/emit! udu/fetch-profile))
|
||||
|
||||
(when (:profile storage)
|
||||
(st/emit! udu/fetch-profile))
|
||||
|
||||
(mf/mount (mf/element ui/app) (dom/get-element "app"))
|
||||
(mf/mount (mf/element modal) (dom/get-element "modal"))
|
||||
|
||||
(on-navigate router cpath)))
|
||||
(mf/mount (mf/element ui/app) (dom/get-element "app"))
|
||||
(mf/mount (mf/element modal) (dom/get-element "modal")))
|
||||
|
||||
(defn ^:export init
|
||||
[]
|
||||
|
@ -73,7 +67,6 @@
|
|||
|
||||
(defn reinit
|
||||
[]
|
||||
(remove-watch html-history/path ::main)
|
||||
(mf/unmount (dom/get-element "app"))
|
||||
(mf/unmount (dom/get-element "modal"))
|
||||
(init-ui))
|
||||
|
|
|
@ -65,7 +65,7 @@
|
|||
(ptk/reify ::clear-user-data
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(select-keys state [:route :router :session-id]))
|
||||
(select-keys state [:route :router :session-id :history]))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state s]
|
||||
|
|
|
@ -141,7 +141,9 @@
|
|||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(->> (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
|
||||
[projects]
|
||||
|
@ -208,7 +210,9 @@
|
|||
(watch [_ state stream]
|
||||
(let [params {:team-id team-id}]
|
||||
(->> (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
|
||||
[recent-files]
|
||||
|
|
|
@ -72,7 +72,8 @@
|
|||
(ptk/reify ::finalize
|
||||
ptk/WatchEvent
|
||||
(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))))
|
||||
|
||||
;; --- Handle: Presence
|
||||
|
|
|
@ -140,9 +140,17 @@
|
|||
(rx/first)
|
||||
(rx/map (fn [[file users project pages]]
|
||||
(bundle-fetched file users project pages)))
|
||||
(rx/catch (fn [{:keys [type] :as error}]
|
||||
(when (= :not-found type)
|
||||
(rx/of (rt/nav :not-found)))))))))
|
||||
(rx/catch (fn [{:keys [type code] :as error}]
|
||||
(cond
|
||||
(= :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
|
||||
[file users project pages]
|
||||
|
|
|
@ -83,7 +83,7 @@
|
|||
(group-by :backend (vals db)))))
|
||||
(add-watch fontsdb "main"
|
||||
(fn [_ _ _ db]
|
||||
(ts/schedule-on-idle #(materialize-fontsview db))))
|
||||
(ts/schedule #(materialize-fontsview db))))
|
||||
|
||||
(defn register!
|
||||
[backend fonts]
|
||||
|
|
|
@ -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)))))
|
|
@ -13,11 +13,13 @@
|
|||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.i18n :as i18n :refer [t tr]]
|
||||
[uxbox.util.router :as rt]
|
||||
[uxbox.util.timers :as ts]
|
||||
[uxbox.util.time :as dt]))
|
||||
|
||||
;; --- Grid Item Thumbnail
|
||||
|
||||
(mf/defc grid-item-thumbnail
|
||||
{::mf/wrap [#(mf/deferred % ts/schedule-on-idle)]}
|
||||
[{:keys [file] :as props}]
|
||||
[:div.grid-item-th
|
||||
[:& exports/page-svg {:data (:data 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}]]))
|
|
@ -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)
|
||||
[:div.library-page-cards-container
|
||||
(for [item items]
|
||||
|
|
|
@ -167,7 +167,7 @@
|
|||
:type "text"
|
||||
:placeholder (t locale "ds.search.placeholder")
|
||||
:default-value search-term-not-nil
|
||||
:autoComplete "off"
|
||||
:auto-complete "off"
|
||||
:on-focus on-search-focus
|
||||
:on-change on-search-change
|
||||
:ref #(when % (set! (.-value %) search-term-not-nil))}]
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
[uxbox.main.ui.shapes.attrs :as attrs]
|
||||
[uxbox.main.ui.shapes.common :as common]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.timers :as ts]
|
||||
[uxbox.util.interop :as itr]
|
||||
[uxbox.util.geom.matrix :as gmt]
|
||||
[uxbox.util.geom.point :as gpt]))
|
||||
|
@ -53,7 +54,8 @@
|
|||
[shape-wrapper]
|
||||
(let [frame-shape (frame-shape shape-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}
|
||||
[props]
|
||||
(let [shape (unchecked-get props "shape")
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
[uxbox.main.store :as st]
|
||||
[uxbox.main.streams :as ms]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.object :as obj]
|
||||
[uxbox.util.geom.point :as gpt]
|
||||
[uxbox.util.geom.matrix :as gmt]
|
||||
[uxbox.util.debug :refer [debug?]]))
|
||||
|
@ -32,49 +33,74 @@
|
|||
60)
|
||||
|
||||
(mf/defc control-item
|
||||
[{:keys [class on-click r cy cx] :as props}]
|
||||
[:circle
|
||||
{:class-name class
|
||||
:on-mouse-down on-click
|
||||
:r r
|
||||
:style {:fillOpacity "1"
|
||||
:strokeWidth "1px"
|
||||
{::mf/wrap-props false}
|
||||
[props]
|
||||
(let [class (obj/get props "class")
|
||||
on-click (obj/get props "on-click")
|
||||
r (obj/get props "r")
|
||||
cx (obj/get props "cx")
|
||||
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"}
|
||||
:fill "#ffffff"
|
||||
:stroke "#1FDEA7"
|
||||
:cx cx
|
||||
:cy cy}])
|
||||
:fill "#ffffff"
|
||||
:stroke "#1FDEA7"
|
||||
:cx cx
|
||||
: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")
|
||||
|
||||
(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
|
||||
[{:keys [cx cy position on-mouse-down rotation zoom]}]
|
||||
(when (#{:top-left :top-right :bottom-left :bottom-right} position)
|
||||
(let [size (/ 20 zoom)
|
||||
rotation (or rotation 0)
|
||||
x (- cx (if (#{:top-left :bottom-left} position) size 0))
|
||||
y (- cy (if (#{:top-left :top-right} position) size 0))
|
||||
angle (case position
|
||||
:top-left 0
|
||||
:top-right 90
|
||||
:bottom-right 180
|
||||
:bottom-left 270)]
|
||||
[:rect {:style {:cursor (str/format rotate-cursor-svg (str (+ 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/wrap-props false}
|
||||
[props]
|
||||
(let [cx (obj/get props "cx")
|
||||
cy (obj/get props "cy")
|
||||
position (obj/get props "position")
|
||||
on-mouse-down (obj/get props "on-mouse-down")
|
||||
rotation (obj/get props "rotation")
|
||||
zoom (obj/get props "zoom")]
|
||||
(when (contains? rotation-handler-positions position)
|
||||
(let [size (/ 20 zoom)
|
||||
rotation (or rotation 0)
|
||||
x (- cx (if (#{:top-left :bottom-left} position) size 0))
|
||||
y (- cy (if (#{:top-left :top-right} position) size 0))
|
||||
angle (case position
|
||||
:top-left 0
|
||||
:top-right 90
|
||||
:bottom-right 180
|
||||
:bottom-left 270)]
|
||||
[: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
|
||||
[{:keys [shape zoom on-resize on-rotate] :as props}]
|
||||
(let [{:keys [x y width height rotation] :as shape} (geom/shape->rect-shape shape)
|
||||
{::mf/wrap-props false}
|
||||
[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)
|
||||
|
||||
transform (geom/transform-matrix shape)
|
||||
|
||||
resize-handlers {:top [(+ x (/ width 2 )) y]
|
||||
:right [(+ x width) (+ y (/ height 2))]
|
||||
:bottom [(+ x (/ width 2)) (+ y height)]
|
||||
|
@ -83,7 +109,7 @@
|
|||
:top-right [(+ x width) y]
|
||||
:bottom-left [x (+ y height)]
|
||||
:bottom-right [(+ x width) (+ y height)]}]
|
||||
|
||||
|
||||
[:g.controls
|
||||
[:rect.main {:transform transform
|
||||
:x (- x 1) :y (- y 1)
|
||||
|
@ -95,17 +121,15 @@
|
|||
|
||||
(for [[position [cx cy]] resize-handlers]
|
||||
(let [tp (gpt/transform (gpt/point cx cy) transform)]
|
||||
[:* {:key (str "fragment-" (name position))}
|
||||
[:& rotation-handler {:key (str "rotation-" (name position))
|
||||
:cx (:x tp)
|
||||
[:* {:key (name position)}
|
||||
[:& rotation-handler {:cx (:x tp)
|
||||
:cy (:y tp)
|
||||
:position position
|
||||
:rotation (:rotation shape)
|
||||
:zoom zoom
|
||||
:on-mouse-down on-rotate}]
|
||||
|
||||
[:& control-item {:key (str "resize-" (name position))
|
||||
:class (name position)
|
||||
[:& control-item {:class (name position)
|
||||
:on-click #(on-resize position %)
|
||||
:r (/ radius zoom)
|
||||
:cx (:x tp)
|
||||
|
@ -170,6 +194,7 @@
|
|||
[{:keys [shapes selected zoom objects] :as props}]
|
||||
(let [shape (geom/selection-rect shapes)
|
||||
shape-center (geom/center shape)
|
||||
|
||||
on-resize #(do (dom/stop-propagation %2)
|
||||
(st/emit! (dw/start-resize %1 selected shape objects)))
|
||||
|
||||
|
@ -189,11 +214,14 @@
|
|||
(let [shape-id (:id shape)
|
||||
shape (geom/transform-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'
|
||||
:zoom zoom
|
||||
:on-rotate on-rotate
|
||||
|
|
|
@ -10,21 +10,23 @@
|
|||
|
||||
(ns uxbox.main.ui.workspace.sidebar.layers
|
||||
(:require
|
||||
[rumext.alpha :as mf]
|
||||
[okulary.core :as l]
|
||||
[uxbox.common.data :as d]
|
||||
[rumext.alpha :as mf]
|
||||
[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.workspace :as dw]
|
||||
[uxbox.main.refs :as refs]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.ui.hooks :as hooks]
|
||||
[uxbox.main.ui.keyboard :as kbd]
|
||||
[uxbox.main.ui.shapes.icon :as icon]
|
||||
[uxbox.util.object :as obj]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.perf :as perf]
|
||||
[uxbox.common.uuid :as uuid]
|
||||
[uxbox.util.i18n :as i18n :refer [t]]))
|
||||
[uxbox.util.timers :as ts]
|
||||
[uxbox.util.i18n :as i18n :refer [t]]
|
||||
[uxbox.util.perf :as perf]))
|
||||
|
||||
;; --- Helpers
|
||||
|
||||
|
@ -230,28 +232,16 @@
|
|||
|
||||
(mf/defc frame-wrapper
|
||||
{::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]
|
||||
[:> 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/wrap [mf/memo]}
|
||||
[]
|
||||
{::mf/wrap [#(mf/memo % =)]}
|
||||
[{:keys [objects] :as props}]
|
||||
(let [selected (mf/deref refs/selected-shapes)
|
||||
objects (mf/deref layers-objects)
|
||||
root (get objects uuid/zero)]
|
||||
;; [:& perf/profiler {:label "layers-tree" :enabled false}
|
||||
[:ul.element-list
|
||||
(for [[index id] (reverse (d/enumerate (:shapes root)))]
|
||||
(let [obj (get objects id)]
|
||||
|
@ -269,16 +259,30 @@
|
|||
:objects objects
|
||||
: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
|
||||
|
||||
;; NOTE: we need to consider using something like react window for
|
||||
;; only render visible items instead of all.
|
||||
|
||||
(mf/defc layers-toolbox
|
||||
{:wrap [mf/memo]}
|
||||
[{: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))]
|
||||
[:div#layers.tool-window
|
||||
[:div.tool-window-bar
|
||||
|
@ -286,4 +290,5 @@
|
|||
[:span (:name page)]
|
||||
#_[:div.tool-window-close {:on-click on-click} i/close]]
|
||||
[:div.tool-window-content
|
||||
[:& layers-tree {:key (:id page)}]]]))
|
||||
[:& layers-tree-wrapper {:key (:id page)
|
||||
:objects (:objects data)}]]]))
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
;; --- Options
|
||||
|
||||
(mf/defc shape-options
|
||||
{::mf/wrap [#(mf/throttle % 60)]}
|
||||
[{:keys [shape] :as props}]
|
||||
[:div
|
||||
(case (:type shape)
|
||||
|
|
|
@ -17,10 +17,21 @@
|
|||
[uxbox.main.ui.modal :as modal]
|
||||
[uxbox.main.ui.workspace.colorpicker :refer [colorpicker-modal]]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.object :as obj]
|
||||
[uxbox.util.math :as math]
|
||||
[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/wrap [#(mf/memo' % fill-menu-memo-equals?)]}
|
||||
[{:keys [shape] :as props}]
|
||||
(let [locale (i18n/use-locale)
|
||||
|
||||
|
|
|
@ -21,9 +21,10 @@
|
|||
[uxbox.util.math :as math]
|
||||
[uxbox.util.i18n :refer [t] :as i18n]))
|
||||
|
||||
|
||||
;; -- User/drawing coords
|
||||
(defn user-coords-vector [shape]
|
||||
|
||||
(defn user-coords-vector
|
||||
[shape]
|
||||
(let [{sel-x :x sel-y :y :as selrect}
|
||||
(-> shape
|
||||
gsh/shape->path
|
||||
|
@ -35,11 +36,13 @@
|
|||
dy (- rec-y sel-y)]
|
||||
(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)]
|
||||
(-> 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)]
|
||||
(-> shape (gsh/move (gpt/negate dv)))))
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
[uxbox.main.ui.workspace.sidebar.options.stroke :refer [stroke-menu]]))
|
||||
|
||||
(mf/defc options
|
||||
{::mf/wrap [mf/memo]}
|
||||
[{:keys [shape] :as props}]
|
||||
[:div
|
||||
[:& measures-menu {:shape shape}]
|
||||
|
|
|
@ -17,10 +17,27 @@
|
|||
[uxbox.main.ui.modal :as modal]
|
||||
[uxbox.main.ui.workspace.colorpicker :refer [colorpicker-modal]]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.object :as obj]
|
||||
[uxbox.util.i18n :as i18n :refer [tr t]]
|
||||
[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/wrap [#(mf/memo' % stroke-menu-memo-equals?)]}
|
||||
[{:keys [shape] :as props}]
|
||||
(let [locale (i18n/use-locale)
|
||||
show-options (not= (:stroke-style shape) :none)
|
||||
|
|
|
@ -102,6 +102,7 @@
|
|||
(-> (rx/take-until stoper ms/mouse-position)
|
||||
(rx/subscribe #(on-point dom reference %))))))))
|
||||
|
||||
|
||||
;; --- Viewport
|
||||
|
||||
(declare remote-user-cursors)
|
||||
|
|
53
frontend/src/uxbox/util/browser_history.js
Normal file
53
frontend/src/uxbox/util/browser_history.js
Normal 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);
|
||||
}
|
||||
});
|
|
@ -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)))))
|
|
@ -465,7 +465,8 @@
|
|||
|
||||
(declare transform-apply-modifiers)
|
||||
|
||||
(defn selection-rect-shape [shape]
|
||||
(defn selection-rect-shape
|
||||
[shape]
|
||||
(-> shape
|
||||
(transform-apply-modifiers)
|
||||
(shape->rect-shape)))
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
});
|
|
@ -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))
|
|
@ -7,16 +7,19 @@
|
|||
(ns uxbox.util.router
|
||||
(:refer-clojure :exclude [resolve])
|
||||
(:require
|
||||
[beicon.core :as rx]
|
||||
[rumext.alpha :as mf]
|
||||
[reitit.core :as r]
|
||||
[goog.events :as e]
|
||||
[cuerdas.core :as str]
|
||||
[potok.core :as ptk]
|
||||
[uxbox.common.data :as d]
|
||||
[uxbox.util.html.history :as html-history])
|
||||
[uxbox.util.browser-history :as bhistory]
|
||||
[uxbox.common.data :as d])
|
||||
(:import
|
||||
goog.Uri
|
||||
goog.Uri.QueryData))
|
||||
|
||||
;; --- API
|
||||
;; --- Router API
|
||||
|
||||
(defn- parse-query-data
|
||||
[^QueryData qdata]
|
||||
|
@ -50,10 +53,17 @@
|
|||
(.setQueryData uri qdt)
|
||||
(.toString uri))))))
|
||||
|
||||
(defn init
|
||||
(defn create
|
||||
[routes]
|
||||
(r/router routes))
|
||||
|
||||
(defn initialize-router
|
||||
[routes]
|
||||
(ptk/reify ::initialize-router
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc state :router (create routes)))))
|
||||
|
||||
(defn query-params
|
||||
"Given goog.Uri, read query parameters into Clojure map."
|
||||
[^goog.Uri uri]
|
||||
|
@ -63,13 +73,6 @@
|
|||
(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 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."
|
||||
|
@ -84,18 +87,54 @@
|
|||
|
||||
;; --- Navigate (Event)
|
||||
|
||||
(deftype Navigate [id params qparams]
|
||||
(deftype Navigate [id params qparams replace]
|
||||
ptk/EffectEvent
|
||||
(effect [_ state stream]
|
||||
(let [router (:router state)]
|
||||
(navigate! router id params qparams))))
|
||||
(let [router (:router state)
|
||||
history (:history state)
|
||||
path (resolve router id params qparams)]
|
||||
(if ^boolean replace
|
||||
(bhistory/replace-token! history path)
|
||||
(bhistory/set-token! history path)))))
|
||||
|
||||
(defn nav
|
||||
([id] (nav id nil nil))
|
||||
([id params] (nav id params nil))
|
||||
([id params qparams]
|
||||
{:pre [(keyword? id)]}
|
||||
(Navigate. id params qparams)))
|
||||
([id params qparams] (Navigate. id params qparams false)))
|
||||
|
||||
(defn nav'
|
||||
([id] (nav id nil nil))
|
||||
([id params] (nav id params nil))
|
||||
([id params qparams] (Navigate. id params qparams true)))
|
||||
|
||||
(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)))))))
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -2,7 +2,10 @@
|
|||
;; 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) 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
|
||||
(:require [beicon.core :as rx]))
|
||||
|
@ -29,3 +32,12 @@
|
|||
(reify rx/IDisposable
|
||||
(-dispose [_]
|
||||
(js/cancelIdleCallback sem)))))
|
||||
|
||||
(defn raf
|
||||
[f]
|
||||
(js/window.requestAnimationFrame f))
|
||||
|
||||
(defn idle-then-raf
|
||||
[f]
|
||||
(schedule-on-idle #(raf f)))
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue