Merge remote-tracking branch 'origin/staging' into develop

This commit is contained in:
Alejandro Alonso 2024-09-05 09:37:16 +02:00
commit e189dc965d
24 changed files with 603 additions and 374 deletions

View file

@ -21,10 +21,12 @@
[app.main.repo :as rp]
[app.util.i18n :as i18n :refer [tr]]
[app.util.router :as rt]
[app.util.storage :refer [storage]]
[app.util.storage :as s]
[beicon.v2.core :as rx]
[potok.v2.core :as ptk]))
(declare update-profile-props)
;; --- SCHEMAS
(def ^:private
@ -49,14 +51,14 @@
(defn get-current-team-id
[profile]
(let [team-id (::current-team-id @storage)]
(let [team-id (::current-team-id @s/storage)]
(or team-id (:default-team-id profile))))
(defn set-current-team!
[team-id]
(if (nil? team-id)
(swap! storage dissoc ::current-team-id)
(swap! storage assoc ::current-team-id team-id)))
(swap! s/storage dissoc ::current-team-id)
(swap! s/storage assoc ::current-team-id team-id)))
;; --- EVENT: fetch-teams
@ -76,9 +78,9 @@
;; if not, dissoc it from storage.
(let [ids (into #{} (map :id) teams)]
(when-let [ctid (::current-team-id @storage)]
(when-let [ctid (::current-team-id @s/storage)]
(when-not (contains? ids ctid)
(swap! storage dissoc ::current-team-id)))))))
(swap! s/storage dissoc ::current-team-id)))))))
(defn fetch-teams
[]
@ -129,10 +131,10 @@
(effect [_ state _]
(let [profile (:profile state)
email (:email profile)
previous-profile (:profile @storage)
previous-profile (:profile @s/storage)
previous-email (:email previous-profile)]
(when profile
(swap! storage assoc :profile profile)
(swap! s/storage assoc :profile profile)
(i18n/set-locale! (:lang profile))
(when (not= previous-email email)
(set-current-team! nil)))))))
@ -152,9 +154,15 @@
profile. The profile can proceed from standard login or from
accepting invitation, or third party auth signup or singin."
[profile]
(letfn [(get-redirect-event []
(let [team-id (get-current-team-id profile)]
(rt/nav' :dashboard-projects {:team-id team-id})))]
(letfn [(get-redirect-events []
(let [team-id (get-current-team-id profile)
welcome-file-id (get-in profile [:props :welcome-file-id])]
(if (some? welcome-file-id)
(rx/of
(rt/nav' :workspace {:project-id (:default-project-id profile)
:file-id welcome-file-id})
(update-profile-props {:welcome-file-id nil}))
(rx/of (rt/nav' :dashboard-projects {:team-id team-id})))))]
(ptk/reify ::logged-in
ev/Event
@ -171,10 +179,11 @@
ptk/WatchEvent
(watch [_ _ _]
(when (is-authenticated? profile)
(->> (rx/of (profile-fetched profile)
(fetch-teams)
(get-redirect-event)
(ws/initialize))
(->> (rx/concat
(rx/of (profile-fetched profile)
(fetch-teams)
(ws/initialize))
(get-redirect-events))
(rx/observe-on :async)))))))
(declare login-from-register)
@ -311,7 +320,7 @@
ptk/EffectEvent
(effect [_ _ _]
;; We prefer to keek some stuff in the storage like the current-team-id and the profile
(set-current-team! nil)))))
(swap! s/storage (constantly {}))))))
(defn logout
([] (logout {}))

View file

@ -79,6 +79,7 @@
[app.util.http :as http]
[app.util.i18n :as i18n :refer [tr]]
[app.util.router :as rt]
[app.util.storage :refer [storage]]
[app.util.timers :as tm]
[app.util.webapi :as wapi]
[beicon.v2.core :as rx]
@ -335,6 +336,7 @@
ptk/UpdateEvent
(update [_ state]
(assoc state
:recent-colors (:recent-colors @storage)
:workspace-ready? false
:current-file-id file-id
:current-project-id project-id

View file

@ -48,6 +48,7 @@
[app.util.color :as uc]
[app.util.i18n :refer [tr]]
[app.util.router :as rt]
[app.util.storage :as s]
[app.util.time :as dt]
[beicon.v2.core :as rx]
[cuerdas.core :as str]
@ -132,16 +133,21 @@
(defn add-recent-color
[color]
(dm/assert!
"expected valid recent color map"
(ctc/check-recent-color! color))
(ctc/valid-recent-color? color))
(ptk/reify ::add-recent-color
ptk/WatchEvent
(watch [it _ _]
(let [changes (-> (pcb/empty-changes it)
(pcb/add-recent-color color))]
(rx/of (dch/commit-changes changes))))))
ptk/UpdateEvent
(update [_ state]
(let [file-id (:current-file-id state)]
(update state :recent-colors ctc/add-recent-color file-id color)))
ptk/EffectEvent
(effect [_ state _]
(let [recent-colors (:recent-colors state)]
(swap! s/storage assoc :recent-colors recent-colors)))))
(def clear-color-for-rename
(ptk/reify ::clear-color-for-rename
@ -168,8 +174,11 @@
(dm/assert!
"expected valid parameters"
(and (ctc/check-color! color)
(uuid? file-id)))
(ctc/valid-color? color))
(dm/assert!
"expected file-id"
(uuid? file-id))
(ptk/reify ::update-color
ptk/WatchEvent

View file

@ -236,9 +236,10 @@
=))
(def workspace-recent-colors
(l/derived (fn [data]
(get data :recent-colors []))
workspace-data))
(l/derived (fn [state]
(when-let [file-id (:current-file-id state)]
(dm/get-in state [:recent-colors file-id])))
st/state))
(def workspace-recent-fonts
(l/derived (fn [data]

View file

@ -44,7 +44,30 @@
(mf/defc main-page
{::mf/props :obj}
[{:keys [route profile]}]
(let [{:keys [data params]} route]
(let [{:keys [data params]} route
props (get profile :props)
show-question-modal?
(and (contains? cf/flags :onboarding)
(not (:onboarding-viewed props))
(not (contains? props :onboarding-questions)))
show-newsletter-modal?
(and (contains? cf/flags :onboarding)
(not (:onboarding-viewed props))
(not (contains? props :newsletter-updates))
(contains? props :onboarding-questions))
show-team-modal?
(and (contains? cf/flags :onboarding)
(not (:onboarding-viewed props))
(not (contains? props :onboarding-team-id))
(contains? props :newsletter-updates))
show-release-modal?
(and (contains? cf/flags :onboarding)
(:onboarding-viewed props)
(not= (:release-notes-viewed props) (:main cf/version))
(not= "0.0" (:main cf/version)))]
[:& (mf/provider ctx/current-route) {:value route}
(case (:name data)
(:auth-login
@ -84,42 +107,19 @@
#_[:& app.main.ui.onboarding/onboarding-templates-modal]
#_[:& app.main.ui.onboarding/onboarding-modal]
#_[:& app.main.ui.onboarding.team-choice/onboarding-team-modal]
(when-let [props (get profile :props)]
(let [show-question-modal?
(and (contains? cf/flags :onboarding)
(not (:onboarding-viewed props))
(not (contains? props :onboarding-questions)))
show-newsletter-modal?
(and (contains? cf/flags :onboarding)
(not (:onboarding-viewed props))
(not (contains? props :newsletter-updates))
(contains? props :onboarding-questions))
(cond
show-question-modal?
[:& questions-modal]
show-team-modal?
(and (contains? cf/flags :onboarding)
(not (:onboarding-viewed props))
(not (contains? props :onboarding-team-id))
(contains? props :newsletter-updates))
show-newsletter-modal?
[:& onboarding-newsletter]
show-release-modal?
(and (contains? cf/flags :onboarding)
(:onboarding-viewed props)
(not= (:release-notes-viewed props) (:main cf/version))
(not= "0.0" (:main cf/version)))]
show-team-modal?
[:& onboarding-team-modal {:go-to-team? true}]
(cond
show-question-modal?
[:& questions-modal]
show-newsletter-modal?
[:& onboarding-newsletter]
show-team-modal?
[:& onboarding-team-modal]
show-release-modal?
[:& release-notes-modal {:version (:main cf/version)}])))
show-release-modal?
[:& release-notes-modal {:version (:main cf/version)}])
[:& dashboard-page {:route route :profile profile}]]
:viewer
@ -154,6 +154,20 @@
page-id (some-> params :query :page-id uuid)
layout (some-> params :query :layout keyword)]
[:? {}
(when (cf/external-feature-flag "onboarding-03" "test")
(cond
show-question-modal?
[:& questions-modal]
show-newsletter-modal?
[:& onboarding-newsletter]
show-team-modal?
[:& onboarding-team-modal {:go-to-team? false}]
show-release-modal?
[:& release-notes-modal {:version (:main cf/version)}]))
[:& workspace-page {:project-id project-id
:file-id file-id
:page-id page-id

View file

@ -39,7 +39,8 @@
form (fm/use-form :schema schema:register-form
:initial initial)
submitted? (mf/use-state false)
submitted?
(mf/use-state false)
on-error
(mf/use-fn
@ -176,7 +177,9 @@
::mf/private true}
[{:keys [params on-success-callback]}]
(let [form (fm/use-form :schema schema:register-validate-form :initial params)
submitted? (mf/use-state false)
submitted?
(mf/use-state false)
on-success
(mf/use-fn
@ -208,7 +211,13 @@
(mf/deps on-success on-error)
(fn [form _]
(reset! submitted? true)
(let [params (:clean-data @form)]
(let [create-welcome-file?
(cf/external-feature-flag "onboarding-03" "test")
params
(cond-> (:clean-data @form)
create-welcome-file? (assoc :create-welcome-file true))]
(->> (rp/cmd! :register-profile params)
(rx/finalize #(reset! submitted? false))
(rx/subs! on-success on-error)))))]

View file

@ -519,8 +519,10 @@
@include bodySmallTypography;
color: var(--modal-title-foreground-color);
}
.custom-input-checkbox {
// TODO: This fix is temporary, the error is caused by the
// cascading order of the compiled css files.
// https://tree.taiga.io/project/penpot/task/8658
.custom-input-checkbox.custom-input-checkbox {
align-items: flex-start;
}

View file

@ -168,7 +168,9 @@
[{:keys [default-project-id profile project-id team-id]}]
(let [templates (mf/deref builtin-templates)
templates (mf/with-memo [templates]
(filterv #(not= (:id %) "tutorial-for-beginners") templates))
(filterv #(and
(not= (:id %) "welcome")
(not= (:id %) "tutorial-for-beginners")) templates))
route (mf/deref refs/route)
route-name (get-in route [:data :name])

View file

@ -294,19 +294,21 @@
`key` for new values."
[key default]
(let [id (mf/use-id)
state (mf/use-state (get @storage key default))
state* (mf/use-state #(get @storage key default))
state (deref state*)
stream (mf/with-memo [id]
(->> mbc/stream
(rx/filter #(not= (:id %) id))
(rx/filter #(= (:type %) key))
(rx/map deref)))]
(mf/with-effect [@state key id]
(mbc/emit! id key @state)
(swap! storage assoc key @state))
(mf/with-effect [state key id]
(mbc/emit! id key state)
(swap! storage assoc key state))
(use-stream stream (partial reset! state))
state))
(use-stream stream (partial reset! state*))
state*))
(defonce ^:private intersection-subject (rx/subject))
(defonce ^:private intersection-observer

View file

@ -6,6 +6,7 @@
(ns app.main.ui.hooks.resize
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.geom.point :as gpt]
[app.common.logging :as log]
@ -20,6 +21,15 @@
(def last-resize-type nil)
(defn- get-initial-state
[initial file-id key]
(let [saved (dm/get-in @storage [::state file-id key])]
(d/nilv saved initial)))
(defn- update-persistent-state
[data file-id key size]
(update-in data [::state file-id] assoc key size))
(defn set-resize-type! [type]
(set! last-resize-type type))
@ -28,26 +38,28 @@
(use-resize-hook key initial min-val max-val axis negate? resize-type nil))
([key initial min-val max-val axis negate? resize-type on-change-size]
(let [current-file-id (mf/use-ctx ctx/current-file-id)
size-state (mf/use-state (or (get-in @storage [::saved-resize current-file-id key]) initial))
parent-ref (mf/use-ref nil)
(let [file-id (mf/use-ctx ctx/current-file-id)
dragging-ref (mf/use-ref false)
current-size* (mf/use-state #(get-initial-state initial file-id key))
current-size (deref current-size*)
parent-ref (mf/use-ref nil)
dragging-ref (mf/use-ref false)
start-size-ref (mf/use-ref nil)
start-ref (mf/use-ref nil)
start-ref (mf/use-ref nil)
on-pointer-down
(mf/use-callback
(mf/deps @size-state)
(mf/use-fn
(mf/deps current-size)
(fn [event]
(dom/capture-pointer event)
(mf/set-ref-val! start-size-ref @size-state)
(mf/set-ref-val! start-size-ref current-size)
(mf/set-ref-val! dragging-ref true)
(mf/set-ref-val! start-ref (dom/get-client-position event))
(set! last-resize-type resize-type)))
on-lost-pointer-capture
(mf/use-callback
(mf/use-fn
(fn [event]
(dom/release-pointer event)
(mf/set-ref-val! start-size-ref nil)
@ -56,40 +68,39 @@
(set! last-resize-type nil)))
on-pointer-move
(mf/use-callback
(mf/deps min-val max-val negate?)
(mf/use-fn
(mf/deps min-val max-val negate? file-id key)
(fn [event]
(when (mf/ref-val dragging-ref)
(let [start (mf/ref-val start-ref)
pos (dom/get-client-position event)
pos (dom/get-client-position event)
delta (-> (gpt/to-vec start pos)
(cond-> negate? gpt/negate)
(get axis))
start-size (mf/ref-val start-size-ref)
new-size (-> (+ start-size delta) (max min-val) (min max-val))]
(reset! size-state new-size)
(swap! storage assoc-in [::saved-resize current-file-id key] new-size)
(when on-change-size (on-change-size new-size))))))
(reset! current-size* new-size)
(swap! storage update-persistent-state file-id key new-size)))))
set-size
(mf/use-callback
(mf/deps on-change-size)
(mf/use-fn
(mf/deps on-change-size file-id key)
(fn [new-size]
(let [new-size (mth/clamp new-size min-val max-val)]
(reset! size-state new-size)
(swap! storage assoc-in [::saved-resize current-file-id key] new-size)
(when on-change-size (on-change-size new-size)))))]
(reset! current-size* new-size)
(swap! storage update-persistent-state file-id key new-size))))]
(mf/use-effect
(fn []
(when on-change-size (on-change-size @size-state))))
(mf/with-effect [on-change-size current-size]
(when on-change-size
(on-change-size current-size)))
{:on-pointer-down on-pointer-down
:on-lost-pointer-capture on-lost-pointer-capture
:on-pointer-move on-pointer-move
:parent-ref parent-ref
:set-size set-size
:size @size-state})))
:size current-size})))
(defn use-resize-observer
[callback]

View file

@ -66,7 +66,7 @@
(mf/defc team-form-step-2
{::mf/props :obj}
[{:keys [name on-back]}]
[{:keys [name on-back go-to-team?]}]
(let [initial (mf/use-memo
#(do {:role "editor"
:name name}))
@ -85,7 +85,8 @@
(let [team-id (:id response)]
(st/emit! (du/update-profile-props {:onboarding-team-id team-id
:onboarding-viewed true})
(rt/nav :dashboard-projects {:team-id team-id})))))
(when go-to-team?
(rt/nav :dashboard-projects {:team-id team-id}))))))
on-error
(mf/use-fn
@ -240,7 +241,7 @@
(mf/defc onboarding-team-modal
{::mf/props :obj}
[]
[{:keys [go-to-team?]}]
(let [name* (mf/use-state nil)
name (deref name*)
@ -262,6 +263,6 @@
[:& left-sidebar]
[:div {:class (stl/css :separator)}]
(if name
[:& team-form-step-2 {:name name :on-back on-back}]
[:& team-form-step-2 {:name name :on-back on-back :go-to-team? go-to-team?}]
[:& team-form-step-1 {:on-submit on-submit}])]]))

View file

@ -6,42 +6,80 @@
(ns app.util.storage
(:require
["lodash/debounce" :as ldebounce]
[app.common.exceptions :as ex]
[app.common.transit :as t]
[app.util.globals :as g]
[app.util.timers :as tm]))
[cuerdas.core :as str]))
(defn- persist
[storage prev curr]
(run! (fn [key]
(let [prev* (get prev key)
curr* (get curr key)]
(when (not= curr* prev*)
(tm/schedule-on-idle
#(if (some? curr*)
(.setItem ^js storage (t/encode-str key) (t/encode-str curr*))
(.removeItem ^js storage (t/encode-str key)))))))
;; Using ex/ignoring because can receive a DOMException like this when
;; importing the code as a library: Failed to read the 'localStorage'
;; property from 'Window': Storage is disabled inside 'data:' URLs.
(defonce ^:private local-storage
(ex/ignoring (unchecked-get g/global "localStorage")))
(into #{} (concat (keys curr)
(keys prev)))))
(defn- encode-key
[k]
(assert (keyword? k) "key must be keyword")
(let [kns (namespace k)
kn (name k)]
(str "penpot:" kns "/" kn)))
(defn- decode-key
[k]
(when (str/starts-with? k "penpot:")
(let [k (subs k 7)]
(if (str/starts-with? k "/")
(keyword (subs k 1))
(let [[kns kn] (str/split k "/" 2)]
(keyword kns kn))))))
(defn- lookup-by-index
[result index]
(try
(let [key (.key ^js local-storage index)
key' (decode-key key)]
(if key'
(let [val (.getItem ^js local-storage key)]
(assoc! result key' (t/decode-str val)))
result))
(catch :default _
result)))
(defn- load
[storage]
(when storage
(let [len (.-length ^js storage)]
(reduce (fn [res index]
(let [key (.key ^js storage index)
val (.getItem ^js storage key)]
(try
(assoc res (t/decode-str key) (t/decode-str val))
(catch :default _e
res))))
{}
(range len)))))
[]
(when (some? local-storage)
(let [length (.-length ^js local-storage)]
(loop [index 0
result (transient {})]
(if (< index length)
(recur (inc index)
(lookup-by-index result index))
(persistent! result))))))
;; Using ex/ignoring because can receive a DOMException like this when importing the code as a library:
;; Failed to read the 'localStorage' property from 'Window': Storage is disabled inside 'data:' URLs.
(defonce storage (atom (load (ex/ignoring (unchecked-get g/global "localStorage")))))
(defonce ^:private latest-state (load))
(add-watch storage :persistence #(persist js/localStorage %3 %4))
(defn- on-change*
[curr-state]
(let [prev-state latest-state]
(try
(run! (fn [key]
(let [prev-val (get prev-state key)
curr-val (get curr-state key)]
(when-not (identical? curr-val prev-val)
(if (some? curr-val)
(.setItem ^js local-storage (encode-key key) (t/encode-str curr-val))
(.removeItem ^js local-storage (encode-key key))))))
(into #{} (concat (keys curr-state)
(keys prev-state))))
(finally
(set! latest-state curr-state)))))
(defonce on-change
(ldebounce on-change* 2000 #js {:leading false :trailing true}))
(defonce storage (atom latest-state))
(add-watch storage :persistence
(fn [_ _ _ curr-state]
(on-change curr-state)))