mirror of
https://github.com/penpot/penpot.git
synced 2025-06-10 23:31:37 +02:00
✨ Store some profile props on browser global storage
This commit is contained in:
parent
c8caca77a3
commit
cdcff62232
3 changed files with 81 additions and 60 deletions
|
@ -10,6 +10,7 @@
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
[app.common.exceptions :as ex]
|
[app.common.exceptions :as ex]
|
||||||
[app.common.schema :as sm]
|
[app.common.schema :as sm]
|
||||||
|
[app.common.types.plugins :refer [schema:plugin-data]]
|
||||||
[app.common.uuid :as uuid]
|
[app.common.uuid :as uuid]
|
||||||
[app.config :as cf]
|
[app.config :as cf]
|
||||||
[app.db :as db]
|
[app.db :as db]
|
||||||
|
@ -40,22 +41,19 @@
|
||||||
(declare strip-private-attrs)
|
(declare strip-private-attrs)
|
||||||
(declare verify-password)
|
(declare verify-password)
|
||||||
|
|
||||||
(defn clean-email
|
(def schema:props
|
||||||
"Clean and normalizes email address string"
|
[:map {:title "ProfileProps"}
|
||||||
[email]
|
[:plugins {:optional true} schema:plugin-data]
|
||||||
(let [email (str/lower email)
|
[:newsletter-updates {:optional true} ::sm/boolean]
|
||||||
email (if (str/starts-with? email "mailto:")
|
[:newsletter-news {:optional true} ::sm/boolean]
|
||||||
(subs email 7)
|
[:onboarding-team-id {:optional true} ::sm/uuid]
|
||||||
email)
|
[:onboarding-viewed {:optional true} ::sm/boolean]
|
||||||
email (if (or (str/starts-with? email "<")
|
[:v2-info-shown {:optional true} ::sm/boolean]
|
||||||
(str/ends-with? email ">"))
|
[:welcome-file-id {:optional true} [:maybe ::sm/boolean]]
|
||||||
(str/trim email "<>")
|
[:release-notes-viewed {:optional true}
|
||||||
email)]
|
[::sm/text {:max 100}]]])
|
||||||
email))
|
|
||||||
|
|
||||||
(def ^:private
|
(def schema:profile
|
||||||
schema:profile
|
|
||||||
(sm/define
|
|
||||||
[:map {:title "Profile"}
|
[:map {:title "Profile"}
|
||||||
[:id ::sm/uuid]
|
[:id ::sm/uuid]
|
||||||
[:fullname [::sm/word-string {:max 250}]]
|
[:fullname [::sm/word-string {:max 250}]]
|
||||||
|
@ -68,8 +66,20 @@
|
||||||
[:modified-at {:optional true} ::sm/inst]
|
[:modified-at {:optional true} ::sm/inst]
|
||||||
[:default-project-id {:optional true} ::sm/uuid]
|
[:default-project-id {:optional true} ::sm/uuid]
|
||||||
[:default-team-id {:optional true} ::sm/uuid]
|
[:default-team-id {:optional true} ::sm/uuid]
|
||||||
[:props {:optional true}
|
[:props {:optional true} schema:props]])
|
||||||
[:map-of {:title "ProfileProps"} :keyword :any]]]))
|
|
||||||
|
(defn clean-email
|
||||||
|
"Clean and normalizes email address string"
|
||||||
|
[email]
|
||||||
|
(let [email (str/lower email)
|
||||||
|
email (if (str/starts-with? email "mailto:")
|
||||||
|
(subs email 7)
|
||||||
|
email)
|
||||||
|
email (if (or (str/starts-with? email "<")
|
||||||
|
(str/ends-with? email ">"))
|
||||||
|
(str/trim email "<>")
|
||||||
|
email)]
|
||||||
|
email))
|
||||||
|
|
||||||
;; --- QUERY: Get profile (own)
|
;; --- QUERY: Get profile (own)
|
||||||
|
|
||||||
|
@ -351,14 +361,12 @@
|
||||||
:extra-data ptoken})
|
:extra-data ptoken})
|
||||||
nil))
|
nil))
|
||||||
|
|
||||||
|
|
||||||
;; --- MUTATION: Update Profile Props
|
;; --- MUTATION: Update Profile Props
|
||||||
|
|
||||||
(def ^:private
|
(def ^:private
|
||||||
schema:update-profile-props
|
schema:update-profile-props
|
||||||
(sm/define
|
|
||||||
[:map {:title "update-profile-props"}
|
[:map {:title "update-profile-props"}
|
||||||
[:props [:map-of :keyword :any]]]))
|
[:props schema:props]])
|
||||||
|
|
||||||
(defn update-profile-props
|
(defn update-profile-props
|
||||||
[{:keys [::db/conn] :as cfg} profile-id props]
|
[{:keys [::db/conn] :as cfg} profile-id props]
|
||||||
|
|
|
@ -11,7 +11,6 @@
|
||||||
[app.main.data.dashboard :as dd]
|
[app.main.data.dashboard :as dd]
|
||||||
[app.main.data.events :as ev]
|
[app.main.data.events :as ev]
|
||||||
[app.main.data.modal :as modal]
|
[app.main.data.modal :as modal]
|
||||||
[app.main.data.users :as du]
|
|
||||||
[app.main.refs :as refs]
|
[app.main.refs :as refs]
|
||||||
[app.main.store :as st]
|
[app.main.store :as st]
|
||||||
[app.main.ui.dashboard.grid :refer [line-grid]]
|
[app.main.ui.dashboard.grid :refer [line-grid]]
|
||||||
|
@ -24,6 +23,7 @@
|
||||||
[app.util.i18n :as i18n :refer [tr]]
|
[app.util.i18n :as i18n :refer [tr]]
|
||||||
[app.util.keyboard :as kbd]
|
[app.util.keyboard :as kbd]
|
||||||
[app.util.router :as rt]
|
[app.util.router :as rt]
|
||||||
|
[app.util.storage :as storage]
|
||||||
[app.util.time :as dt]
|
[app.util.time :as dt]
|
||||||
[cuerdas.core :as str]
|
[cuerdas.core :as str]
|
||||||
[okulary.core :as l]
|
[okulary.core :as l]
|
||||||
|
@ -54,24 +54,25 @@
|
||||||
:data-testid "new-project-button"}
|
:data-testid "new-project-button"}
|
||||||
(tr "dashboard.new-project")]]))
|
(tr "dashboard.new-project")]]))
|
||||||
|
|
||||||
(mf/defc team-hero
|
(mf/defc team-hero*
|
||||||
{::mf/wrap [mf/memo]}
|
{::mf/wrap [mf/memo]
|
||||||
[{:keys [team close-fn] :as props}]
|
::mf/props :obj}
|
||||||
|
[{:keys [team on-close]}]
|
||||||
(let [on-nav-members-click (mf/use-fn #(st/emit! (dd/go-to-team-members)))
|
(let [on-nav-members-click (mf/use-fn #(st/emit! (dd/go-to-team-members)))
|
||||||
|
|
||||||
on-invite-click
|
on-invite
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
(mf/deps team)
|
(mf/deps team)
|
||||||
(fn []
|
(fn []
|
||||||
(st/emit! (modal/show {:type :invite-members
|
(st/emit! (modal/show {:type :invite-members
|
||||||
:team team
|
:team team
|
||||||
:origin :hero}))))
|
:origin :hero}))))
|
||||||
on-close-click
|
on-close'
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
(mf/deps close-fn)
|
(mf/deps on-close)
|
||||||
(fn [event]
|
(fn [event]
|
||||||
(dom/prevent-default event)
|
(dom/prevent-default event)
|
||||||
(close-fn)))]
|
(on-close event)))]
|
||||||
|
|
||||||
[:div {:class (stl/css :team-hero)}
|
[:div {:class (stl/css :team-hero)}
|
||||||
[:div {:class (stl/css :img-wrapper)}
|
[:div {:class (stl/css :img-wrapper)}
|
||||||
|
@ -85,11 +86,11 @@
|
||||||
[:a {:on-click on-nav-members-click} (tr "dasboard.team-hero.management")]]
|
[:a {:on-click on-nav-members-click} (tr "dasboard.team-hero.management")]]
|
||||||
[:button
|
[:button
|
||||||
{:class (stl/css :btn-primary :invite)
|
{:class (stl/css :btn-primary :invite)
|
||||||
:on-click on-invite-click}
|
:on-click on-invite}
|
||||||
(tr "onboarding.choice.team-up.invite-members")]]
|
(tr "onboarding.choice.team-up.invite-members")]]
|
||||||
|
|
||||||
[:button {:class (stl/css :close)
|
[:button {:class (stl/css :close)
|
||||||
:on-click on-close-click
|
:on-click on-close'
|
||||||
:aria-label (tr "labels.close")}
|
:aria-label (tr "labels.close")}
|
||||||
close-icon]]))
|
close-icon]]))
|
||||||
|
|
||||||
|
@ -292,26 +293,27 @@
|
||||||
(sort-by :modified-at)
|
(sort-by :modified-at)
|
||||||
(reverse))
|
(reverse))
|
||||||
recent-map (mf/deref recent-files-ref)
|
recent-map (mf/deref recent-files-ref)
|
||||||
props (some-> profile (get :props {}))
|
|
||||||
you-owner? (get-in team [:permissions :is-owner])
|
you-owner? (get-in team [:permissions :is-owner])
|
||||||
you-admin? (get-in team [:permissions :is-admin])
|
you-admin? (get-in team [:permissions :is-admin])
|
||||||
can-invite? (or you-owner? you-admin?)
|
can-invite? (or you-owner? you-admin?)
|
||||||
team-hero? (and can-invite?
|
|
||||||
(:team-hero? props true)
|
show-team-hero* (mf/use-state #(get storage/global ::show-team-hero true))
|
||||||
(not (:is-default team)))
|
show-team-hero? (deref show-team-hero*)
|
||||||
|
|
||||||
is-my-penpot (= (:default-team-id profile) (:id team))
|
is-my-penpot (= (:default-team-id profile) (:id team))
|
||||||
|
is-defalt-team? (:is-default team)
|
||||||
|
|
||||||
team-id (:id team)
|
team-id (:id team)
|
||||||
|
|
||||||
close-banner
|
on-close
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
(fn []
|
(fn []
|
||||||
(st/emit! (du/update-profile-props {:team-hero? false})
|
(reset! show-team-hero* false)
|
||||||
(ptk/data-event ::ev/event {::ev/name "dont-show-team-up-hero"
|
(st/emit! (ptk/data-event ::ev/event {::ev/name "dont-show-team-up-hero"
|
||||||
::ev/origin "dashboard"}))))
|
::ev/origin "dashboard"}))))]
|
||||||
|
|
||||||
show-team-hero? (and (not is-my-penpot) team-hero?)]
|
(mf/with-effect [show-team-hero?]
|
||||||
|
(swap! storage/global assoc ::show-team-hero show-team-hero?))
|
||||||
|
|
||||||
(mf/with-effect [team]
|
(mf/with-effect [team]
|
||||||
(let [tname (if (:is-default team)
|
(let [tname (if (:is-default team)
|
||||||
|
@ -328,13 +330,18 @@
|
||||||
[:& header]
|
[:& header]
|
||||||
[:div {:class (stl/css :projects-container)}
|
[:div {:class (stl/css :projects-container)}
|
||||||
[:*
|
[:*
|
||||||
(when team-hero?
|
(when (and show-team-hero?
|
||||||
[:& team-hero {:team team :close-fn close-banner}])
|
can-invite?
|
||||||
|
(not is-defalt-team?))
|
||||||
|
[:> team-hero* {:team team :on-close on-close}])
|
||||||
|
|
||||||
[:div {:class (stl/css-case :dashboard-container true
|
[:div {:class (stl/css-case :dashboard-container true
|
||||||
:no-bg true
|
:no-bg true
|
||||||
:dashboard-projects true
|
:dashboard-projects true
|
||||||
:with-team-hero show-team-hero?)}
|
:with-team-hero (and (not is-my-penpot)
|
||||||
|
(not is-defalt-team?)
|
||||||
|
show-team-hero?
|
||||||
|
can-invite?))}
|
||||||
(for [{:keys [id] :as project} projects]
|
(for [{:keys [id] :as project} projects]
|
||||||
(let [files (when recent-map
|
(let [files (when recent-map
|
||||||
(->> (vals recent-map)
|
(->> (vals recent-map)
|
||||||
|
|
|
@ -12,7 +12,6 @@
|
||||||
[app.main.data.dashboard :as dd]
|
[app.main.data.dashboard :as dd]
|
||||||
[app.main.data.events :as ev]
|
[app.main.data.events :as ev]
|
||||||
[app.main.data.modal :as modal]
|
[app.main.data.modal :as modal]
|
||||||
[app.main.data.users :as du]
|
|
||||||
[app.main.refs :as refs]
|
[app.main.refs :as refs]
|
||||||
[app.main.store :as st]
|
[app.main.store :as st]
|
||||||
[app.main.ui.icons :as i]
|
[app.main.ui.icons :as i]
|
||||||
|
@ -20,6 +19,7 @@
|
||||||
[app.util.i18n :refer [tr]]
|
[app.util.i18n :refer [tr]]
|
||||||
[app.util.keyboard :as kbd]
|
[app.util.keyboard :as kbd]
|
||||||
[app.util.router :as rt]
|
[app.util.router :as rt]
|
||||||
|
[app.util.storage :as storage]
|
||||||
[okulary.core :as l]
|
[okulary.core :as l]
|
||||||
[potok.v2.core :as ptk]
|
[potok.v2.core :as ptk]
|
||||||
[rumext.v2 :as mf]))
|
[rumext.v2 :as mf]))
|
||||||
|
@ -60,17 +60,11 @@
|
||||||
:template template
|
:template template
|
||||||
:on-finish-import on-finish}))))
|
:on-finish-import on-finish}))))
|
||||||
|
|
||||||
(mf/defc title
|
(mf/defc title*
|
||||||
{::mf/wrap-props false}
|
{::mf/props :obj
|
||||||
[{:keys [collapsed]}]
|
::mf/private true}
|
||||||
(let [on-click
|
[{:keys [on-click is-collapsed]}]
|
||||||
(mf/use-fn
|
(let [on-key-down
|
||||||
(mf/deps collapsed)
|
|
||||||
(fn [_event]
|
|
||||||
(let [props {:builtin-templates-collapsed-status (not collapsed)}]
|
|
||||||
(st/emit! (du/update-profile-props props)))))
|
|
||||||
|
|
||||||
on-key-down
|
|
||||||
(mf/use-fn
|
(mf/use-fn
|
||||||
(mf/deps on-click)
|
(mf/deps on-click)
|
||||||
(fn [event]
|
(fn [event]
|
||||||
|
@ -86,7 +80,7 @@
|
||||||
:on-key-down on-key-down}
|
:on-key-down on-key-down}
|
||||||
[:span {:class (stl/css :title-text)}
|
[:span {:class (stl/css :title-text)}
|
||||||
(tr "dashboard.libraries-and-templates")]
|
(tr "dashboard.libraries-and-templates")]
|
||||||
(if ^boolean collapsed
|
(if ^boolean is-collapsed
|
||||||
[:span {:class (stl/css :title-icon :title-icon-collapsed)}
|
[:span {:class (stl/css :title-icon :title-icon-collapsed)}
|
||||||
arrow-icon]
|
arrow-icon]
|
||||||
[:span {:class (stl/css :title-icon)}
|
[:span {:class (stl/css :title-icon)}
|
||||||
|
@ -180,8 +174,12 @@
|
||||||
"dashboard-project")
|
"dashboard-project")
|
||||||
(name route-name))
|
(name route-name))
|
||||||
|
|
||||||
props (:props profile)
|
collapsed* (mf/use-state
|
||||||
collapsed (:builtin-templates-collapsed-status props false)
|
#(get storage/global ::collapsed))
|
||||||
|
collapsed (deref collapsed*)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
can-move (mf/use-state {:left false :right true})
|
can-move (mf/use-state {:left false :right true})
|
||||||
|
|
||||||
total (count templates)
|
total (count templates)
|
||||||
|
@ -192,6 +190,11 @@
|
||||||
move-left (fn [] (dom/scroll-by! (mf/ref-val content-ref) -300 0))
|
move-left (fn [] (dom/scroll-by! (mf/ref-val content-ref) -300 0))
|
||||||
move-right (fn [] (dom/scroll-by! (mf/ref-val content-ref) 300 0))
|
move-right (fn [] (dom/scroll-by! (mf/ref-val content-ref) 300 0))
|
||||||
|
|
||||||
|
on-toggle-collapse
|
||||||
|
(mf/use-fn
|
||||||
|
(fn [_event]
|
||||||
|
(swap! collapsed* not)))
|
||||||
|
|
||||||
update-can-move
|
update-can-move
|
||||||
(fn [scroll-left scroll-available client-width]
|
(fn [scroll-left scroll-available client-width]
|
||||||
(reset! can-move {:left (> scroll-left 0)
|
(reset! can-move {:left (> scroll-left 0)
|
||||||
|
@ -231,12 +234,15 @@
|
||||||
(.dispatchEvent content (js/Event. "scroll")))))
|
(.dispatchEvent content (js/Event. "scroll")))))
|
||||||
|
|
||||||
(mf/with-effect [profile collapsed]
|
(mf/with-effect [profile collapsed]
|
||||||
|
(swap! storage/global assoc ::collapsed collapsed)
|
||||||
|
|
||||||
(when (and profile (not collapsed))
|
(when (and profile (not collapsed))
|
||||||
(st/emit! (dd/fetch-builtin-templates))))
|
(st/emit! (dd/fetch-builtin-templates))))
|
||||||
|
|
||||||
[:div {:class (stl/css-case :dashboard-templates-section true
|
[:div {:class (stl/css-case :dashboard-templates-section true
|
||||||
:collapsed collapsed)}
|
:collapsed collapsed)}
|
||||||
[:& title {:collapsed collapsed}]
|
[:> title* {:on-click on-toggle-collapse
|
||||||
|
:is-collapsed collapsed}]
|
||||||
|
|
||||||
[:div {:class (stl/css :content)
|
[:div {:class (stl/css :content)
|
||||||
:on-scroll on-scroll
|
:on-scroll on-scroll
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue