Make feature resolved on team load

That simplifies features retrieval to simple get
This commit is contained in:
Andrey Antukh 2025-04-07 07:50:40 +02:00
parent b8107ee497
commit bc957893f4
23 changed files with 224 additions and 285 deletions

View file

@ -25,7 +25,6 @@
(let [claims (-> {} (let [claims (-> {}
(into (::session/token-claims request)) (into (::session/token-claims request))
(into (::actoken/token-claims request)))] (into (::actoken/token-claims request)))]
{:request/path (:path request) {:request/path (:path request)
:request/method (:method request) :request/method (:method request)
:request/params (:params request) :request/params (:params request)

View file

@ -328,7 +328,7 @@
(-> (cfeat/get-team-enabled-features cf/flags team) (-> (cfeat/get-team-enabled-features cf/flags team)
(cfeat/check-client-features! (:features params)) (cfeat/check-client-features! (:features params))
(cfeat/check-file-features! (:features file) (:features params))) (cfeat/check-file-features! (:features file)))
;; This operation is needed for backward comapatibility with frontends that ;; This operation is needed for backward comapatibility with frontends that
;; does not support pointer-map resolution mechanism; this just resolves the ;; does not support pointer-map resolution mechanism; this just resolves the
@ -490,7 +490,7 @@
_ (-> (cfeat/get-team-enabled-features cf/flags team) _ (-> (cfeat/get-team-enabled-features cf/flags team)
(cfeat/check-client-features! (:features params)) (cfeat/check-client-features! (:features params))
(cfeat/check-file-features! (:features file) (:features params))) (cfeat/check-file-features! (:features file)))
page (binding [pmap/*load-fn* (partial feat.fdata/load-pointer cfg file-id)] page (binding [pmap/*load-fn* (partial feat.fdata/load-pointer cfg file-id)]
(let [page-id (or page-id (-> file :data :pages first)) (let [page-id (or page-id (-> file :data :pages first))
@ -737,7 +737,7 @@
(-> (cfeat/get-team-enabled-features cf/flags team) (-> (cfeat/get-team-enabled-features cf/flags team)
(cfeat/check-client-features! (:features params)) (cfeat/check-client-features! (:features params))
(cfeat/check-file-features! (:features file) (:features params))) (cfeat/check-file-features! (:features file)))
(binding [pmap/*load-fn* (partial feat.fdata/load-pointer cfg id)] (binding [pmap/*load-fn* (partial feat.fdata/load-pointer cfg id)]
{:name (:name file) {:name (:name file)

View file

@ -91,9 +91,6 @@
:project-id project-id) :project-id project-id)
team-id (:id team) team-id (:id team)
;; When we create files, we only need to respect the team
;; features, because some features can be enabled
;; globally, but the team is still not migrated properly.
features (-> (cfeat/get-team-enabled-features cf/flags team) features (-> (cfeat/get-team-enabled-features cf/flags team)
(cfeat/check-client-features! (:features params))) (cfeat/check-client-features! (:features params)))
@ -107,7 +104,7 @@
params (-> params params (-> params
(assoc :profile-id profile-id) (assoc :profile-id profile-id)
(assoc :features (set/difference features cfeat/frontend-only-features)))] (assoc :features features))]
(quotes/check! cfg {::quotes/id ::quotes/files-per-project (quotes/check! cfg {::quotes/id ::quotes/files-per-project
::quotes/team-id team-id ::quotes/team-id team-id

View file

@ -212,7 +212,7 @@
(-> (cfeat/get-team-enabled-features cf/flags team) (-> (cfeat/get-team-enabled-features cf/flags team)
(cfeat/check-client-features! (:features params)) (cfeat/check-client-features! (:features params))
(cfeat/check-file-features! (:features file) (:features params))) (cfeat/check-file-features! (:features file)))
{:file-id file-id {:file-id file-id
:revn (:revn file) :revn (:revn file)

View file

@ -142,7 +142,7 @@
features (-> (cfeat/get-team-enabled-features cf/flags team) features (-> (cfeat/get-team-enabled-features cf/flags team)
(cfeat/check-client-features! (:features params)) (cfeat/check-client-features! (:features params))
(cfeat/check-file-features! (:features file) (:features params))) (cfeat/check-file-features! (:features file)))
changes (if changes-with-metadata changes (if changes-with-metadata
(->> changes-with-metadata (mapcat :changes) vec) (->> changes-with-metadata (mapcat :changes) vec)

View file

@ -85,12 +85,11 @@
;; be applied (per example backend can operate in both modes with or ;; be applied (per example backend can operate in both modes with or
;; without migration applied) ;; without migration applied)
(def no-migration-features (def no-migration-features
(-> #{"fdata/objects-map" (-> #{"layout/grid"
"fdata/pointer-map"
"layout/grid"
"fdata/shape-data-type" "fdata/shape-data-type"
"design-tokens/v1"} "design-tokens/v1"}
(into frontend-only-features))) (into frontend-only-features)
(into backend-only-features)))
(sm/register! (sm/register!
^{::sm/type ::features} ^{::sm/type ::features}
@ -158,7 +157,6 @@
team-features (into #{} xf-remove-ephimeral (:features team))] team-features (into #{} xf-remove-ephimeral (:features team))]
(-> enabled-features (-> enabled-features
(set/intersection no-migration-features) (set/intersection no-migration-features)
(set/difference frontend-only-features)
(set/union team-features)))) (set/union team-features))))
(defn check-client-features! (defn check-client-features!
@ -167,6 +165,8 @@
frontend client" frontend client"
[enabled-features client-features] [enabled-features client-features]
(when (set? client-features) (when (set? client-features)
;; Check if client declares support for features enabled on
;; backend side
(let [not-supported (-> enabled-features (let [not-supported (-> enabled-features
(set/difference client-features) (set/difference client-features)
(set/difference frontend-only-features) (set/difference frontend-only-features)
@ -176,14 +176,6 @@
:code :feature-not-supported :code :feature-not-supported
:feature (first not-supported) :feature (first not-supported)
:hint (str/ffmt "client declares no support for '%' features" :hint (str/ffmt "client declares no support for '%' features"
(str/join "," not-supported)))))
(let [not-supported (set/difference client-features supported-features)]
(when (seq not-supported)
(ex/raise :type :restriction
:code :feature-not-supported
:feature (first not-supported)
:hint (str/ffmt "backend does not support '%' features requested by client"
(str/join "," not-supported)))))) (str/join "," not-supported))))))
enabled-features) enabled-features)
@ -194,57 +186,49 @@
supported by the current backend" supported by the current backend"
[enabled-features] [enabled-features]
(let [not-supported (set/difference enabled-features supported-features)] (let [not-supported (set/difference enabled-features supported-features)]
(when (seq not-supported) (when-let [not-supported (first not-supported)]
(ex/raise :type :restriction (ex/raise :type :restriction
:code :feature-not-supported :code :feature-not-supported
:feature (first not-supported) :feature not-supported
:hint (str/ffmt "features '%' not supported" :hint (str/ffmt "feature '%' not supported on this backend" not-supported)))
(str/join "," not-supported))))) enabled-features))
enabled-features)
(defn check-file-features! (defn check-file-features!
"Function used for check feature compability between currently "Function used for check feature compability between currently
enabled features set on backend with the provided featured set by enabled features set on backend with the provided featured set by
the penpot file" the penpot file"
([enabled-features file-features] [enabled-features file-features]
(check-file-features! enabled-features file-features #{})) (let [file-features (into #{} xf-remove-ephimeral file-features)
([enabled-features file-features client-features] not-supported (-> enabled-features
(let [file-features (into #{} xf-remove-ephimeral file-features) (set/difference file-features)
;; We should ignore all features that does not match with the ;; NOTE: we don't want to raise a feature-mismatch
;; `no-migration-features` set because we can't enable them ;; exception for features which don't require an
;; as-is, because they probably need migrations ;; explicit file migration process or has no real
client-features (set/intersection client-features no-migration-features)] ;; effect on file data structure
(let [not-supported (-> enabled-features (set/difference no-migration-features))]
(set/union client-features)
(set/difference file-features)
;; NOTE: we don't want to raise a feature-mismatch
;; exception for features which don't require an
;; explicit file migration process or has no real
;; effect on file data structure
(set/difference no-migration-features))]
(when (seq not-supported)
(ex/raise :type :restriction
:code :file-feature-mismatch
:feature (first not-supported)
:hint (str/ffmt "enabled features '%' not present in file (missing migration)"
(str/join "," not-supported)))))
(check-supported-features! file-features) (when-let [not-supported (first not-supported)]
(ex/raise :type :restriction
:code :file-feature-mismatch
:feature not-supported
:hint (str/ffmt "enabled feature '%' not present in file (missing migration)"
not-supported)))
(let [not-supported (-> file-features (check-supported-features! file-features)
(set/difference enabled-features)
(set/difference client-features)
(set/difference backend-only-features)
(set/difference frontend-only-features))]
(when (seq not-supported) (let [not-supported (-> file-features
(ex/raise :type :restriction (set/difference enabled-features)
:code :file-feature-mismatch (set/difference backend-only-features)
:feature (first not-supported) (set/difference frontend-only-features))]
:hint (str/ffmt "file features '%' not enabled"
(str/join "," not-supported))))))
enabled-features)) ;; Check if file has a feature but that feature is not enabled
(when-let [not-supported (first not-supported)]
(ex/raise :type :restriction
:code :file-feature-mismatch
:feature not-supported
:hint (str/ffmt "file feature '%' not enabled" not-supported))))
enabled-features))
(defn check-teams-compatibility! (defn check-teams-compatibility!
[{source-features :features} {destination-features :features}] [{source-features :features} {destination-features :features}]

View file

@ -15,7 +15,6 @@
[app.main.data.profile :as dp] [app.main.data.profile :as dp]
[app.main.data.websocket :as ws] [app.main.data.websocket :as ws]
[app.main.errors] [app.main.errors]
[app.main.features :as feat]
[app.main.rasterizer :as thr] [app.main.rasterizer :as thr]
[app.main.store :as st] [app.main.store :as st]
[app.main.ui :as ui] [app.main.ui :as ui]
@ -67,7 +66,6 @@
(watch [_ _ stream] (watch [_ _ stream]
(rx/merge (rx/merge
(rx/of (ev/initialize) (rx/of (ev/initialize)
(feat/initialize)
(dp/refresh-profile)) (dp/refresh-profile))
;; Watch for profile deletion events ;; Watch for profile deletion events

View file

@ -13,7 +13,6 @@
[app.common.types.shape-tree :as ctst] [app.common.types.shape-tree :as ctst]
[app.common.uuid :as uuid] [app.common.uuid :as uuid]
[app.main.data.helpers :as dsh] [app.main.data.helpers :as dsh]
[app.main.features :as features]
[app.main.worker :as uw] [app.main.worker :as uw]
[app.util.time :as dt] [app.util.time :as dt]
[beicon.v2.core :as rx] [beicon.v2.core :as rx]
@ -182,8 +181,8 @@
(let [file-id (or file-id (:current-file-id state)) (let [file-id (or file-id (:current-file-id state))
uchg (vec undo-changes) uchg (vec undo-changes)
rchg (vec redo-changes) rchg (vec redo-changes)
features (features/get-team-enabled-features state) features (get state :features)
permissions (:permissions state)] permissions (get state :permissions)]
;; Prevent commit changes by a viewer team member (it really should never happen) ;; Prevent commit changes by a viewer team member (it really should never happen)
(when (:can-edit permissions) (when (:can-edit permissions)

View file

@ -16,7 +16,6 @@
[app.main.data.modal :as modal] [app.main.data.modal :as modal]
[app.main.data.notifications :as ntf] [app.main.data.notifications :as ntf]
[app.main.data.persistence :as-alias dps] [app.main.data.persistence :as-alias dps]
[app.main.features :as features]
[app.main.repo :as rp] [app.main.repo :as rp]
[app.main.router :as rt] [app.main.router :as rt]
[app.main.store :as st] [app.main.store :as st]
@ -107,7 +106,7 @@
(ptk/reify ::show-shared-dialog (ptk/reify ::show-shared-dialog
ptk/WatchEvent ptk/WatchEvent
(watch [_ state _] (watch [_ state _]
(let [features (features/get-team-enabled-features state) (let [features (get state :features)
file (dsh/lookup-file state) file (dsh/lookup-file state)
data (get file :data)] data (get file :data)]
@ -164,8 +163,8 @@
(ptk/reify ::export-files (ptk/reify ::export-files
ptk/WatchEvent ptk/WatchEvent
(watch [_ state _] (watch [_ state _]
(let [features (features/get-team-enabled-features state) (let [features (get state :features)
team-id (:current-team-id state)] team-id (get state :current-team-id)]
(->> (rx/from files) (->> (rx/from files)
(rx/mapcat (rx/mapcat
(fn [file] (fn [file]

View file

@ -19,7 +19,6 @@
[app.main.data.helpers :as dsh] [app.main.data.helpers :as dsh]
[app.main.data.modal :as modal] [app.main.data.modal :as modal]
[app.main.data.websocket :as dws] [app.main.data.websocket :as dws]
[app.main.features :as features]
[app.main.repo :as rp] [app.main.repo :as rp]
[app.util.i18n :as i18n :refer [tr]] [app.util.i18n :as i18n :refer [tr]]
[app.util.sse :as sse] [app.util.sse :as sse]
@ -497,7 +496,7 @@
base-name (tr "dashboard.new-file-prefix") base-name (tr "dashboard.new-file-prefix")
name (or name name (or name
(cfh/generate-unique-name base-name unames :immediate-suffix? true)) (cfh/generate-unique-name base-name unames :immediate-suffix? true))
features (-> (features/get-team-enabled-features state) features (-> (get state :features)
(set/difference cfeat/frontend-only-features)) (set/difference cfeat/frontend-only-features))
params (-> params params (-> params
(assoc :name name) (assoc :name name)

View file

@ -12,7 +12,6 @@
[app.common.schema :as sm] [app.common.schema :as sm]
[app.main.data.event :as ev] [app.main.data.event :as ev]
[app.main.data.modal :as modal] [app.main.data.modal :as modal]
[app.main.features :as features]
[app.main.repo :as rp] [app.main.repo :as rp]
[beicon.v2.core :as rx] [beicon.v2.core :as rx]
[potok.v2.core :as ptk])) [potok.v2.core :as ptk]))
@ -47,7 +46,7 @@
(ptk/reify ::export-files (ptk/reify ::export-files
ptk/WatchEvent ptk/WatchEvent
(watch [_ state _] (watch [_ state _]
(let [features (features/get-team-enabled-features state) (let [features (get state :features)
team-id (:current-team-id state) team-id (:current-team-id state)
evname (if (= format :legacy-zip) evname (if (= format :legacy-zip)
"export-standard-files" "export-standard-files"

View file

@ -101,7 +101,7 @@
(let [permissions (get team :permissions) (let [permissions (get team :permissions)
features (get team :features)] features (get team :features)]
(rx/of #(assoc % :permissions permissions) (rx/of #(assoc % :permissions permissions)
(features/initialize (or features #{})) (features/initialize features)
(fetch-members team-id)))))) (fetch-members team-id))))))
ptk/EffectEvent ptk/EffectEvent
@ -255,12 +255,12 @@
(dm/assert! (string? name)) (dm/assert! (string? name))
(ptk/reify ::create-team (ptk/reify ::create-team
ptk/WatchEvent ptk/WatchEvent
(watch [it state _] (watch [it _ _]
(let [{:keys [on-success on-error] (let [{:keys [on-success on-error]
:or {on-success identity :or {on-success identity
on-error rx/throw}} (meta params) on-error rx/throw}} (meta params)
features (features/get-enabled-features state) features features/global-enabled-features
params {:name name :features features}] params {:name name :features features}]
(->> (rp/cmd! :create-team (with-meta params (meta it))) (->> (rp/cmd! :create-team (with-meta params (meta it)))
(rx/tap on-success) (rx/tap on-success)
(rx/map team-created) (rx/map team-created)
@ -272,11 +272,11 @@
[{:keys [name emails role] :as params}] [{:keys [name emails role] :as params}]
(ptk/reify ::create-team-with-invitations (ptk/reify ::create-team-with-invitations
ptk/WatchEvent ptk/WatchEvent
(watch [it state _] (watch [it _ _]
(let [{:keys [on-success on-error] (let [{:keys [on-success on-error]
:or {on-success identity :or {on-success identity
on-error rx/throw}} (meta params) on-error rx/throw}} (meta params)
features (features/get-enabled-features state) features features/global-enabled-features
params {:name name params {:name name
:emails emails :emails emails
:role role :role role

View file

@ -184,7 +184,7 @@
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(let [team-id (:id team) (let [team-id (:id team)
team {:members users}] team (assoc team :members users)]
(-> state (-> state
(assoc :share-links share-links) (assoc :share-links share-links)
(assoc :current-team-id team-id) (assoc :current-team-id team-id)

View file

@ -205,30 +205,29 @@
(d/index-by :id)))))) (d/index-by :id))))))
(defn- fetch-libraries (defn- fetch-libraries
[file-id] [file-id features]
(ptk/reify ::fetch-libries (ptk/reify ::fetch-libries
ptk/WatchEvent ptk/WatchEvent
(watch [_ state _] (watch [_ _ _]
(let [features (features/get-team-enabled-features state)] (->> (rp/cmd! :get-file-libraries {:file-id file-id})
(->> (rp/cmd! :get-file-libraries {:file-id file-id}) (rx/mapcat
(rx/mapcat (fn [libraries]
(fn [libraries] (rx/concat
(rx/concat (rx/of (libraries-fetched file-id libraries))
(rx/of (libraries-fetched file-id libraries)) (rx/merge
(rx/merge (->> (rx/from libraries)
(->> (rx/from libraries) (rx/merge-map
(rx/merge-map (fn [{:keys [id synced-at]}]
(fn [{:keys [id synced-at]}] (->> (rp/cmd! :get-file {:id id :features features})
(->> (rp/cmd! :get-file {:id id :features features}) (rx/map #(assoc % :synced-at synced-at :library-of file-id)))))
(rx/map #(assoc % :synced-at synced-at :library-of file-id))))) (rx/mapcat resolve-file)
(rx/mapcat resolve-file) (rx/map library-resolved))
(rx/map library-resolved)) (->> (rx/from libraries)
(->> (rx/from libraries) (rx/map :id)
(rx/map :id) (rx/mapcat (fn [file-id]
(rx/mapcat (fn [file-id] (rp/cmd! :get-file-object-thumbnails {:file-id file-id :tag "component"})))
(rp/cmd! :get-file-object-thumbnails {:file-id file-id :tag "component"}))) (rx/map dwl/library-thumbnails-fetched)))
(rx/map dwl/library-thumbnails-fetched))) (rx/of (check-libraries-synchronozation file-id libraries)))))))))
(rx/of (check-libraries-synchronozation file-id libraries))))))))))
(defn- workspace-initialized (defn- workspace-initialized
[file-id] [file-id]
@ -246,28 +245,16 @@
(fbs/fix-broken-shapes))))) (fbs/fix-broken-shapes)))))
(defn- bundle-fetched (defn- bundle-fetched
[{:keys [features file thumbnails]}] [{:keys [file file-id thumbnails] :as bundle}]
(ptk/reify ::bundle-fetched (ptk/reify ::bundle-fetched
IDeref IDeref
(-deref [_] (-deref [_] bundle)
{:features features
:file file
:thumbnails thumbnails})
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(let [file-id (:id file)] (-> state
(-> state (assoc :thumbnails thumbnails)
(assoc :thumbnails thumbnails) (update :files assoc file-id file)))))
(update :files assoc file-id file))))
ptk/WatchEvent
(watch [_ state _]
(let [team-id (:current-team-id state)
file-id (:id file)]
(rx/of (dwn/initialize team-id file-id)
(dwsl/initialize-shape-layout)
(fetch-libraries file-id))))))
(defn zoom-to-frame (defn zoom-to-frame
[] []
@ -296,47 +283,30 @@
(defn- fetch-bundle (defn- fetch-bundle
"Multi-stage file bundle fetch coordinator" "Multi-stage file bundle fetch coordinator"
[file-id] [file-id features]
(ptk/reify ::fetch-bundle (ptk/reify ::fetch-bundle
ptk/WatchEvent ptk/WatchEvent
(watch [_ state stream] (watch [_ _ stream]
(let [features (features/get-team-enabled-features state) (let [stopper-s (rx/filter (ptk/type? ::finalize-workspace) stream)]
render-wasm? (contains? features "render-wasm/v1") (->> (rx/zip (rp/cmd! :get-file {:id file-id :features features})
stopper-s (rx/filter (ptk/type? ::finalize-workspace) stream) (get-file-object-thumbnails file-id))
team-id (:current-team-id state)] (rx/take 1)
(rx/mapcat
(->> (rx/concat (fn [[file thumbnails]]
;; Firstly load wasm module if it is enabled and fonts (->> (resolve-file file)
(rx/merge (rx/map (fn [file]
(if ^boolean render-wasm? {:file file
(->> (rx/from @wasm/module) :file-id file-id
(rx/ignore)) :features features
(rx/empty)) :thumbnails thumbnails})))))
(rx/map bundle-fetched)
(->> stream
(rx/filter (ptk/type? ::df/fonts-loaded))
(rx/take 1)
(rx/ignore))
(rx/of (df/fetch-fonts team-id)))
;; Then fetch file and thumbnails
(->> (rx/zip (rp/cmd! :get-file {:id file-id :features features})
(get-file-object-thumbnails file-id))
(rx/take 1)
(rx/mapcat
(fn [[file thumbnails]]
(->> (resolve-file file)
(rx/map (fn [file]
{:file file
:features features
:thumbnails thumbnails})))))
(rx/map bundle-fetched)))
(rx/take-until stopper-s)))))) (rx/take-until stopper-s))))))
(defn initialize-workspace (defn initialize-workspace
[team-id file-id] [team-id file-id]
(assert (uuid? team-id) "expected valud uuid for `team-id`") (assert (uuid? team-id) "expected valud uuid for `team-id`")
(assert (uuid? file-id) "expected valud uuid for `file-id`") (assert (uuid? file-id) "expected valud uuid for `file-id`")
(ptk/reify ::initialize-workspace (ptk/reify ::initialize-workspace
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
@ -348,24 +318,51 @@
ptk/WatchEvent ptk/WatchEvent
(watch [_ state stream] (watch [_ state stream]
(log/debug :hint "initialize-workspace" :file-id (dm/str file-id)) (let [stoper-s (rx/filter (ptk/type? ::finalize-workspace) stream)
(let [stoper-s (rx/filter (ptk/type? ::finalize-workspace) stream) rparams (rt/get-params state)
rparams (rt/get-params state)] features (features/get-enabled-features state team-id)
render-wasm? (contains? features "render-wasm/v1")]
(log/debug :hint "initialize-workspace"
:team-id (dm/str team-id)
:file-id (dm/str file-id))
(->> (rx/merge (->> (rx/merge
(rx/of (ntf/hide) (rx/concat
(dcmt/retrieve-comment-threads file-id) ;; Fetch all essential data that should be loaded before the file
(dcmt/fetch-profiles) (rx/merge
(fetch-bundle file-id)) (if ^boolean render-wasm?
(->> (rx/from @wasm/module)
(rx/ignore))
(rx/empty))
(->> stream
(rx/filter (ptk/type? ::df/fonts-loaded))
(rx/take 1)
(rx/ignore))
(rx/of (ntf/hide)
(dcmt/retrieve-comment-threads file-id)
(dcmt/fetch-profiles)
(df/fetch-fonts team-id)))
;; Once the essential data is fetched, lets proceed to
;; fetch teh file bunldle
(rx/of (fetch-bundle file-id features)))
(->> stream (->> stream
(rx/filter (ptk/type? ::bundle-fetched)) (rx/filter (ptk/type? ::bundle-fetched))
(rx/take 1) (rx/take 1)
(rx/map deref) (rx/map deref)
(rx/mapcat (fn [{:keys [file]}] (rx/mapcat
(rx/of (dpj/initialize-project (:project-id file)) (fn [{:keys [file]}]
(-> (workspace-initialized file-id) (rx/of (dpj/initialize-project (:project-id file))
(with-meta {:file-id file-id})))))) (dwn/initialize team-id file-id)
(dwsl/initialize-shape-layout)
(fetch-libraries file-id features)
(-> (workspace-initialized file-id)
(with-meta {:team-id team-id
:file-id file-id}))))))
(->> stream (->> stream
(rx/filter (ptk/type? ::dps/persistence-notification)) (rx/filter (ptk/type? ::dps/persistence-notification))
@ -431,7 +428,6 @@
ptk/WatchEvent ptk/WatchEvent
(watch [_ state _] (watch [_ state _]
(let [project-id (:current-project-id state)] (let [project-id (:current-project-id state)]
(rx/of (dwn/finalize file-id) (rx/of (dwn/finalize file-id)
(dpj/finalize-project project-id) (dpj/finalize-project project-id)
(dwsl/finalize-shape-layout) (dwsl/finalize-shape-layout)
@ -452,8 +448,6 @@
;; Make this event callable through dynamic resolution ;; Make this event callable through dynamic resolution
(defmethod ptk/resolve ::reload-current-file [_ _] (reload-current-file)) (defmethod ptk/resolve ::reload-current-file [_ _] (reload-current-file))
(def ^:private xf:collect-file-media (def ^:private xf:collect-file-media
"Resolve and collect all file media on page objects" "Resolve and collect all file media on page objects"
(comp (map second) (comp (map second)
@ -1419,7 +1413,7 @@
(let [objects (dsh/lookup-page-objects state) (let [objects (dsh/lookup-page-objects state)
selected (->> (dsh/lookup-selected state) selected (->> (dsh/lookup-selected state)
(cfh/clean-loops objects)) (cfh/clean-loops objects))
features (-> (features/get-team-enabled-features state) features (-> (get state :features)
(set/difference cfeat/frontend-only-features)) (set/difference cfeat/frontend-only-features))
file-id (:current-file-id state) file-id (:current-file-id state)
@ -1657,9 +1651,10 @@
objects (dsh/lookup-page-objects state)] objects (dsh/lookup-page-objects state)]
(when-let [shape (get objects selected)] (when-let [shape (get objects selected)]
(let [props (cts/extract-props shape) (let [props (cts/extract-props shape)
features (-> (features/get-team-enabled-features state) features (-> (get state :features)
(set/difference cfeat/frontend-only-features)) (set/difference cfeat/frontend-only-features))
version (-> (dsh/lookup-file state) :version) version (-> (dsh/lookup-file state)
(get :version))
copy-data {:type :copied-props copy-data {:type :copied-props
:features features :features features
@ -1793,8 +1788,8 @@
(ptk/reify ::paste-transit-shapes (ptk/reify ::paste-transit-shapes
ptk/WatchEvent ptk/WatchEvent
(watch [_ state _] (watch [_ state _]
(let [file-id (:current-file-id state) (let [file-id (:current-file-id state)
features (features/get-team-enabled-features state)] features (get state :features)]
(when-not (paste-data-valid? pdata) (when-not (paste-data-valid? pdata)
(ex/raise :type :validation (ex/raise :type :validation
@ -1865,7 +1860,7 @@
(ptk/reify ::paste-transit-props (ptk/reify ::paste-transit-props
ptk/WatchEvent ptk/WatchEvent
(watch [_ state _] (watch [_ state _]
(let [features (features/get-team-enabled-features state) (let [features (get state :features)
selected (dsh/lookup-selected state)] selected (dsh/lookup-selected state)]
(when (paste-data-valid? pdata) (when (paste-data-valid? pdata)

View file

@ -1398,7 +1398,7 @@
ptk/WatchEvent ptk/WatchEvent
(watch [_ state _] (watch [_ state _]
(let [features (features/get-team-enabled-features state)] (let [features (get state :features)]
(rx/concat (rx/concat
(rx/merge (rx/merge
(->> (rp/cmd! :link-file-to-library {:file-id file-id :library-id library-id}) (->> (rp/cmd! :link-file-to-library {:file-id file-id :library-id library-id})

View file

@ -203,9 +203,8 @@
(ptk/reify ::restore-version-from-plugins (ptk/reify ::restore-version-from-plugins
ptk/WatchEvent ptk/WatchEvent
(watch [_ state _] (watch [_ state _]
;; FIXME: revisit this
(let [file (dsh/lookup-file state file-id) (let [file (dsh/lookup-file state file-id)
team-id (:team-id file)] team-id (or (:team-id file) (:current-file-id state))]
(rx/concat (rx/concat
(rx/of (ptk/event ::ev/event {::ev/name "restore-version-plugin"}) (rx/of (ptk/event ::ev/event {::ev/name "restore-version-plugin"})
::dwp/force-persist) ::dwp/force-persist)

View file

@ -8,12 +8,12 @@
"A thin, frontend centric abstraction layer and collection of "A thin, frontend centric abstraction layer and collection of
helpers for `app.common.features` namespace." helpers for `app.common.features` namespace."
(:require (:require
[app.common.data.macros :as dm]
[app.common.features :as cfeat] [app.common.features :as cfeat]
[app.common.logging :as log] [app.common.logging :as log]
[app.config :as cf] [app.config :as cf]
[app.main.store :as st] [app.main.store :as st]
[app.render-wasm :as wasm] [app.render-wasm :as wasm]
[beicon.v2.core :as rx]
[clojure.set :as set] [clojure.set :as set]
[cuerdas.core :as str] [cuerdas.core :as str]
[okulary.core :as l] [okulary.core :as l]
@ -26,38 +26,32 @@
(cfeat/get-enabled-features cf/flags)) (cfeat/get-enabled-features cf/flags))
(defn get-enabled-features (defn get-enabled-features
[state] "An explicit lookup of enabled features for the current team"
(-> (get state :features-runtime #{}) [state team-id]
(set/intersection cfeat/no-migration-features) (let [team (dm/get-in state [:teams team-id])]
(set/union global-enabled-features)))
(defn get-team-enabled-features
[state]
(let [runtime-features (:features-runtime state #{})
team-features (->> (:features-team state #{})
(into #{} cfeat/xf-remove-ephimeral))]
(-> global-enabled-features (-> global-enabled-features
(set/union runtime-features) (set/union (get state :features-runtime #{}))
(set/intersection cfeat/no-migration-features) (set/difference cfeat/no-migration-features)
(set/union team-features)))) (set/union (get team :features)))))
(def features-ref
(l/derived get-team-enabled-features st/state =))
(defn active-feature? (defn active-feature?
"Given a state and feature, check if feature is enabled" "Given a state and feature, check if feature is enabled."
[state feature] [state feature]
(assert (contains? cfeat/supported-features feature) "not supported feature") (assert (contains? cfeat/supported-features feature) "feature not supported")
(or (contains? (get state :features-runtime) feature) (let [runtime-features (get state :features-runtime)
(if (contains? cfeat/no-migration-features feature) enabled-features (get state :features)]
(or (contains? global-enabled-features feature) (or (contains? runtime-features feature)
(contains? (get state :features-team) feature)) (if (contains? cfeat/no-migration-features feature)
(contains? (get state :features-team state) feature)))) (or (contains? global-enabled-features feature)
(contains? enabled-features feature))
(contains? enabled-features feature)))))
(def ^:private features-ref
(l/derived (l/key :features) st/state))
(defn use-feature (defn use-feature
"A react hook that checks if feature is currently enabled" "A react hook that checks if feature is currently enabled"
[feature] [feature]
(assert (contains? cfeat/supported-features feature) "Not supported feature")
(let [enabled-features (mf/deref features-ref)] (let [enabled-features (mf/deref features-ref)]
(contains? enabled-features feature))) (contains? enabled-features feature)))
@ -71,14 +65,16 @@
ptk/UpdateEvent ptk/UpdateEvent
(update [_ state] (update [_ state]
(assert (contains? cfeat/supported-features feature) "not supported feature") (assert (contains? cfeat/supported-features feature) "not supported feature")
(update state :features-runtime (fn [features] (-> state
(if (contains? features feature) (update :features-runtime (fn [features]
(do (if (contains? features feature)
(log/trc :hint "feature disabled" :feature feature) (do
(disj features feature)) (log/trc :hint "feature disabled" :feature feature)
(do (disj features feature))
(log/trc :hint "feature enabled" :feature feature) (do
(conj features feature)))))))) (log/trc :hint "feature enabled" :feature feature)
(conj features feature)))))
(update :features-runtime set/intersection cfeat/no-migration-features)))))
(defn enable-feature (defn enable-feature
[feature] [feature]
@ -90,46 +86,28 @@
state state
(do (do
(log/trc :hint "feature enabled" :feature feature) (log/trc :hint "feature enabled" :feature feature)
(update state :features-runtime (fnil conj #{}) feature)))))) (-> state
(update :features-runtime (fnil conj #{}) feature)
(update :features-runtime set/intersection cfeat/no-migration-features)))))))
(defn initialize (defn initialize
([] (initialize #{})) [features]
([team-features] (ptk/reify ::initialize
(assert (set? team-features) "expected a set of features") ptk/UpdateEvent
(assert (every? string? team-features) "expected a set of strings") (update [_ state]
(let [features (-> global-enabled-features
(set/union (get state :features-runtime #{}))
(set/union features))]
(assoc state :features features)))
(ptk/reify ::initialize ptk/EffectEvent
ptk/UpdateEvent (effect [_ state _]
(update [_ state] (let [features (get state :features)]
(let [runtime-features (get state :features/runtime #{}) (if (contains? features "render-wasm/v1")
team-features (into #{} (wasm/initialize true)
cfeat/xf-supported-features (wasm/initialize false))
team-features)]
(-> state
(assoc :features-runtime runtime-features)
(assoc :features-team team-features))))
ptk/WatchEvent (log/inf :hint "initialized"
(watch [_ _ _] :enabled (str/join "," features)
(when *assert* :runtime (str/join "," (:features-runtime state)))))))
(->> (rx/from cfeat/no-migration-features)
;; text editor v2 isn't enabled by default even in devenv
;; wasm render v1 isn't enabled by default even in devenv
(rx/filter #(not (or (contains? cfeat/backend-only-features %)
(= "text-editor/v2" %)
(= "render-wasm/v1" %)
(= "design-tokens/v1" %))))
(rx/observe-on :async)
(rx/map enable-feature))))
ptk/EffectEvent
(effect [_ state _]
(let [features (get-team-enabled-features state)]
(if (contains? features "render-wasm/v1")
(wasm/initialize true)
(wasm/initialize false))
(log/inf :hint "initialized"
:enabled (str/join "," features)
:runtime (str/join "," (:features-runtime state))))))))

View file

@ -17,7 +17,6 @@
[app.main.data.notifications :as ntf] [app.main.data.notifications :as ntf]
[app.main.data.project :as dpj] [app.main.data.project :as dpj]
[app.main.data.team :as dtm] [app.main.data.team :as dtm]
[app.main.features :as features]
[app.main.fonts :as fonts] [app.main.fonts :as fonts]
[app.main.rasterizer :as thr] [app.main.rasterizer :as thr]
[app.main.refs :as refs] [app.main.refs :as refs]
@ -60,7 +59,7 @@
(->> (wrk/ask! {:cmd :thumbnails/generate-for-file (->> (wrk/ask! {:cmd :thumbnails/generate-for-file
:revn revn :revn revn
:file-id file-id :file-id file-id
:features (features/get-team-enabled-features @st/state)}) :features (get @st/state :features)})
(rx/mapcat (fn [{:keys [fonts] :as result}] (rx/mapcat (fn [{:keys [fonts] :as result}]
(->> (fonts/render-font-styles fonts) (->> (fonts/render-font-styles fonts)
(rx/map (fn [styles] (rx/map (fn [styles]

View file

@ -15,7 +15,6 @@
[app.main.data.modal :as modal] [app.main.data.modal :as modal]
[app.main.data.notifications :as ntf] [app.main.data.notifications :as ntf]
[app.main.errors :as errors] [app.main.errors :as errors]
[app.main.features :as features]
[app.main.store :as st] [app.main.store :as st]
[app.main.ui.components.file-uploader :refer [file-uploader]] [app.main.ui.components.file-uploader :refer [file-uploader]]
[app.main.ui.ds.product.loader :refer [loader*]] [app.main.ui.ds.product.loader :refer [loader*]]
@ -162,29 +161,32 @@
(defn- analyze-entries (defn- analyze-entries
[state entries] [state entries]
(->> (uw/ask-many! (let [features (get @st/state :features)]
{:cmd :analyze-import (->> (uw/ask-many!
:files entries {:cmd :analyze-import
:features @features/features-ref}) :files entries
(rx/mapcat #(rx/delay emit-delay (rx/of %))) :features features})
(rx/filter some?) (rx/mapcat #(rx/delay emit-delay (rx/of %)))
(rx/subs! (rx/filter some?)
(fn [message] (rx/subs!
(swap! state update-with-analyze-result message))))) (fn [message]
(swap! state update-with-analyze-result message))))))
(defn- import-files (defn- import-files
[state project-id entries] [state project-id entries]
(st/emit! (ptk/data-event ::ev/event {::ev/name "import-files" (st/emit! (ptk/data-event ::ev/event {::ev/name "import-files"
:num-files (count entries)})) :num-files (count entries)}))
(->> (uw/ask-many!
{:cmd :import-files (let [features (get @st/state :features)]
:project-id project-id (->> (uw/ask-many!
:files entries {:cmd :import-files
:features @features/features-ref}) :project-id project-id
(rx/filter (comp uuid? :file-id)) :files entries
(rx/subs! :features features})
(fn [message] (rx/filter (comp uuid? :file-id))
(swap! state update-entry-status message))))) (rx/subs!
(fn [message]
(swap! state update-entry-status message))))))
(mf/defc import-entry* (mf/defc import-entry*
{::mf/props :obj {::mf/props :obj

View file

@ -13,7 +13,6 @@
[app.main.data.exports.files :as exports.files] [app.main.data.exports.files :as exports.files]
[app.main.data.workspace :as dw] [app.main.data.workspace :as dw]
[app.main.data.workspace.versions :as dwv] [app.main.data.workspace.versions :as dwv]
[app.main.features :as features]
[app.main.repo :as rp] [app.main.repo :as rp]
[app.main.store :as st] [app.main.store :as st]
[app.main.worker :as uw] [app.main.worker :as uw]
@ -237,7 +236,7 @@
:else :else
(let [file (u/locate-file id) (let [file (u/locate-file id)
features (features/get-team-enabled-features @st/state) features (:features @st/state)
team-id (:current-team-id @st/state) team-id (:current-team-id @st/state)
format (case format format (case format
"zip" :legacy-zip "zip" :legacy-zip

View file

@ -41,7 +41,7 @@
ptk/WatchEvent ptk/WatchEvent
(watch [_ _ _] (watch [_ _ _]
(rx/of (features/initialize (or features #{})))))) (rx/of (features/initialize features)))))
(defn- fetch-team (defn- fetch-team
[& {:keys [file-id]}] [& {:keys [file-id]}]
@ -98,7 +98,7 @@
(ptk/reify ::fetch-objects-bundle (ptk/reify ::fetch-objects-bundle
ptk/WatchEvent ptk/WatchEvent
(watch [_ state _] (watch [_ state _]
(let [features (features/get-team-enabled-features state)] (let [features (get state :features)]
(->> (rx/zip (->> (rx/zip
(repo/cmd! :get-font-variants {:file-id file-id :share-id share-id}) (repo/cmd! :get-font-variants {:file-id file-id :share-id share-id})
(repo/cmd! :get-page {:file-id file-id (repo/cmd! :get-page {:file-id file-id
@ -237,7 +237,7 @@
(ptk/reify ::fetch-components-bundle (ptk/reify ::fetch-components-bundle
ptk/WatchEvent ptk/WatchEvent
(watch [_ state _] (watch [_ state _]
(let [features (features/get-team-enabled-features state)] (let [features (get state :features)]
(->> (repo/cmd! :get-file {:id file-id :features features}) (->> (repo/cmd! :get-file {:id file-id :features features})
(rx/map (fn [file] #(assoc % :file file)))))))) (rx/map (fn [file] #(assoc % :file file))))))))
@ -309,7 +309,6 @@
(defn ^:export init (defn ^:export init
[] []
(st/emit! (features/initialize))
(init-ui)) (init-ui))
(defn reinit (defn reinit

View file

@ -27,7 +27,6 @@
[app.main.data.workspace.selection :as dws] [app.main.data.workspace.selection :as dws]
[app.main.data.workspace.shortcuts] [app.main.data.workspace.shortcuts]
[app.main.errors :as errors] [app.main.errors :as errors]
[app.main.features :as features]
[app.main.repo :as rp] [app.main.repo :as rp]
[app.main.store :as st] [app.main.store :as st]
[app.util.debug :as dbg] [app.util.debug :as dbg]
@ -393,7 +392,7 @@
(ptk/reify ::repair-current-file (ptk/reify ::repair-current-file
ptk/EffectEvent ptk/EffectEvent
(effect [_ state _] (effect [_ state _]
(let [features (features/get-team-enabled-features state) (let [features (:features state)
sid (:session-id state) sid (:session-id state)
file (dsh/lookup-file state) file (dsh/lookup-file state)
@ -430,7 +429,3 @@
(defn ^:export set-shape-ref (defn ^:export set-shape-ref
[id shape-ref] [id shape-ref]
(st/emit! (dw/set-shape-ref id shape-ref))) (st/emit! (dw/set-shape-ref id shape-ref)))
(defn ^:export enable-text-v2
[]
(st/emit! (features/enable-feature "text-editor/v2")))

View file

@ -20,13 +20,12 @@
nil) nil)
(defn ^:export get-enabled [] (defn ^:export get-enabled []
(clj->js (features/get-enabled-features @st/state))) (clj->js features/global-enabled-features))
(defn ^:export get-team-enabled [] (defn ^:export get-team-enabled []
(clj->js (features/get-team-enabled-features @st/state))) (clj->js (get @st/state :features)))
(defn ^:export plugins [] (defn ^:export plugins []
(st/emit! (features/enable-feature "plugins/runtime")) (st/emit! (features/enable-feature "plugins/runtime"))
(plugins/init-plugins-runtime!) (plugins/init-plugins-runtime!)
nil) nil)