diff --git a/backend/src/app/http/errors.clj b/backend/src/app/http/errors.clj index 9647ecec2..eaeb9eef7 100644 --- a/backend/src/app/http/errors.clj +++ b/backend/src/app/http/errors.clj @@ -25,7 +25,6 @@ (let [claims (-> {} (into (::session/token-claims request)) (into (::actoken/token-claims request)))] - {:request/path (:path request) :request/method (:method request) :request/params (:params request) diff --git a/backend/src/app/rpc/commands/files.clj b/backend/src/app/rpc/commands/files.clj index f409b0bc7..39b3d6c02 100644 --- a/backend/src/app/rpc/commands/files.clj +++ b/backend/src/app/rpc/commands/files.clj @@ -328,7 +328,7 @@ (-> (cfeat/get-team-enabled-features cf/flags team) (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 ;; does not support pointer-map resolution mechanism; this just resolves the @@ -490,7 +490,7 @@ _ (-> (cfeat/get-team-enabled-features cf/flags team) (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)] (let [page-id (or page-id (-> file :data :pages first)) @@ -737,7 +737,7 @@ (-> (cfeat/get-team-enabled-features cf/flags team) (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)] {:name (:name file) diff --git a/backend/src/app/rpc/commands/files_create.clj b/backend/src/app/rpc/commands/files_create.clj index 73c8d7909..0502ff487 100644 --- a/backend/src/app/rpc/commands/files_create.clj +++ b/backend/src/app/rpc/commands/files_create.clj @@ -91,9 +91,6 @@ :project-id project-id) 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) (cfeat/check-client-features! (:features params))) @@ -107,7 +104,7 @@ params (-> params (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/team-id team-id diff --git a/backend/src/app/rpc/commands/files_thumbnails.clj b/backend/src/app/rpc/commands/files_thumbnails.clj index c3a5cb5d4..f4c5e3d14 100644 --- a/backend/src/app/rpc/commands/files_thumbnails.clj +++ b/backend/src/app/rpc/commands/files_thumbnails.clj @@ -212,7 +212,7 @@ (-> (cfeat/get-team-enabled-features cf/flags team) (cfeat/check-client-features! (:features params)) - (cfeat/check-file-features! (:features file) (:features params))) + (cfeat/check-file-features! (:features file))) {:file-id file-id :revn (:revn file) diff --git a/backend/src/app/rpc/commands/files_update.clj b/backend/src/app/rpc/commands/files_update.clj index 396f3ffbd..73b4afd3d 100644 --- a/backend/src/app/rpc/commands/files_update.clj +++ b/backend/src/app/rpc/commands/files_update.clj @@ -142,7 +142,7 @@ features (-> (cfeat/get-team-enabled-features cf/flags team) (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-with-metadata (mapcat :changes) vec) diff --git a/common/src/app/common/features.cljc b/common/src/app/common/features.cljc index b396e1937..a56829ef3 100644 --- a/common/src/app/common/features.cljc +++ b/common/src/app/common/features.cljc @@ -85,12 +85,11 @@ ;; be applied (per example backend can operate in both modes with or ;; without migration applied) (def no-migration-features - (-> #{"fdata/objects-map" - "fdata/pointer-map" - "layout/grid" + (-> #{"layout/grid" "fdata/shape-data-type" "design-tokens/v1"} - (into frontend-only-features))) + (into frontend-only-features) + (into backend-only-features))) (sm/register! ^{::sm/type ::features} @@ -158,7 +157,6 @@ team-features (into #{} xf-remove-ephimeral (:features team))] (-> enabled-features (set/intersection no-migration-features) - (set/difference frontend-only-features) (set/union team-features)))) (defn check-client-features! @@ -167,6 +165,8 @@ frontend client" [enabled-features client-features] (when (set? client-features) + ;; Check if client declares support for features enabled on + ;; backend side (let [not-supported (-> enabled-features (set/difference client-features) (set/difference frontend-only-features) @@ -176,14 +176,6 @@ :code :feature-not-supported :feature (first not-supported) :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)))))) enabled-features) @@ -194,57 +186,49 @@ supported by the current backend" [enabled-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 :code :feature-not-supported - :feature (first not-supported) - :hint (str/ffmt "features '%' not supported" - (str/join "," not-supported))))) - enabled-features) + :feature not-supported + :hint (str/ffmt "feature '%' not supported on this backend" not-supported))) + enabled-features)) (defn check-file-features! "Function used for check feature compability between currently enabled features set on backend with the provided featured set by the penpot file" - ([enabled-features file-features] - (check-file-features! enabled-features file-features #{})) - ([enabled-features file-features client-features] - (let [file-features (into #{} xf-remove-ephimeral file-features) - ;; We should ignore all features that does not match with the - ;; `no-migration-features` set because we can't enable them - ;; as-is, because they probably need migrations - client-features (set/intersection client-features no-migration-features)] - (let [not-supported (-> enabled-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))))) + [enabled-features file-features] + (let [file-features (into #{} xf-remove-ephimeral file-features) + not-supported (-> enabled-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))] - (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 - (set/difference enabled-features) - (set/difference client-features) - (set/difference backend-only-features) - (set/difference frontend-only-features))] + (check-supported-features! file-features) - (when (seq not-supported) - (ex/raise :type :restriction - :code :file-feature-mismatch - :feature (first not-supported) - :hint (str/ffmt "file features '%' not enabled" - (str/join "," not-supported)))))) + (let [not-supported (-> file-features + (set/difference enabled-features) + (set/difference backend-only-features) + (set/difference frontend-only-features))] - 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! [{source-features :features} {destination-features :features}] diff --git a/frontend/src/app/main.cljs b/frontend/src/app/main.cljs index e36985b17..b13c47dd8 100644 --- a/frontend/src/app/main.cljs +++ b/frontend/src/app/main.cljs @@ -15,7 +15,6 @@ [app.main.data.profile :as dp] [app.main.data.websocket :as ws] [app.main.errors] - [app.main.features :as feat] [app.main.rasterizer :as thr] [app.main.store :as st] [app.main.ui :as ui] @@ -67,7 +66,6 @@ (watch [_ _ stream] (rx/merge (rx/of (ev/initialize) - (feat/initialize) (dp/refresh-profile)) ;; Watch for profile deletion events diff --git a/frontend/src/app/main/data/changes.cljs b/frontend/src/app/main/data/changes.cljs index 72de74ea9..c63832693 100644 --- a/frontend/src/app/main/data/changes.cljs +++ b/frontend/src/app/main/data/changes.cljs @@ -13,7 +13,6 @@ [app.common.types.shape-tree :as ctst] [app.common.uuid :as uuid] [app.main.data.helpers :as dsh] - [app.main.features :as features] [app.main.worker :as uw] [app.util.time :as dt] [beicon.v2.core :as rx] @@ -182,8 +181,8 @@ (let [file-id (or file-id (:current-file-id state)) uchg (vec undo-changes) rchg (vec redo-changes) - features (features/get-team-enabled-features state) - permissions (:permissions state)] + features (get state :features) + permissions (get state :permissions)] ;; Prevent commit changes by a viewer team member (it really should never happen) (when (:can-edit permissions) diff --git a/frontend/src/app/main/data/common.cljs b/frontend/src/app/main/data/common.cljs index 713c5d198..b202dfcdb 100644 --- a/frontend/src/app/main/data/common.cljs +++ b/frontend/src/app/main/data/common.cljs @@ -16,7 +16,6 @@ [app.main.data.modal :as modal] [app.main.data.notifications :as ntf] [app.main.data.persistence :as-alias dps] - [app.main.features :as features] [app.main.repo :as rp] [app.main.router :as rt] [app.main.store :as st] @@ -107,7 +106,7 @@ (ptk/reify ::show-shared-dialog ptk/WatchEvent (watch [_ state _] - (let [features (features/get-team-enabled-features state) + (let [features (get state :features) file (dsh/lookup-file state) data (get file :data)] @@ -164,8 +163,8 @@ (ptk/reify ::export-files ptk/WatchEvent (watch [_ state _] - (let [features (features/get-team-enabled-features state) - team-id (:current-team-id state)] + (let [features (get state :features) + team-id (get state :current-team-id)] (->> (rx/from files) (rx/mapcat (fn [file] diff --git a/frontend/src/app/main/data/dashboard.cljs b/frontend/src/app/main/data/dashboard.cljs index 57a886340..f43278450 100644 --- a/frontend/src/app/main/data/dashboard.cljs +++ b/frontend/src/app/main/data/dashboard.cljs @@ -19,7 +19,6 @@ [app.main.data.helpers :as dsh] [app.main.data.modal :as modal] [app.main.data.websocket :as dws] - [app.main.features :as features] [app.main.repo :as rp] [app.util.i18n :as i18n :refer [tr]] [app.util.sse :as sse] @@ -497,7 +496,7 @@ base-name (tr "dashboard.new-file-prefix") name (or name (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)) params (-> params (assoc :name name) diff --git a/frontend/src/app/main/data/exports/files.cljs b/frontend/src/app/main/data/exports/files.cljs index 56ab281a7..b89c027fe 100644 --- a/frontend/src/app/main/data/exports/files.cljs +++ b/frontend/src/app/main/data/exports/files.cljs @@ -12,7 +12,6 @@ [app.common.schema :as sm] [app.main.data.event :as ev] [app.main.data.modal :as modal] - [app.main.features :as features] [app.main.repo :as rp] [beicon.v2.core :as rx] [potok.v2.core :as ptk])) @@ -47,7 +46,7 @@ (ptk/reify ::export-files ptk/WatchEvent (watch [_ state _] - (let [features (features/get-team-enabled-features state) + (let [features (get state :features) team-id (:current-team-id state) evname (if (= format :legacy-zip) "export-standard-files" diff --git a/frontend/src/app/main/data/team.cljs b/frontend/src/app/main/data/team.cljs index 2ee1e5828..ce96d45b2 100644 --- a/frontend/src/app/main/data/team.cljs +++ b/frontend/src/app/main/data/team.cljs @@ -101,7 +101,7 @@ (let [permissions (get team :permissions) features (get team :features)] (rx/of #(assoc % :permissions permissions) - (features/initialize (or features #{})) + (features/initialize features) (fetch-members team-id)))))) ptk/EffectEvent @@ -255,12 +255,12 @@ (dm/assert! (string? name)) (ptk/reify ::create-team ptk/WatchEvent - (watch [it state _] + (watch [it _ _] (let [{:keys [on-success on-error] :or {on-success identity on-error rx/throw}} (meta params) - features (features/get-enabled-features state) - params {:name name :features features}] + features features/global-enabled-features + params {:name name :features features}] (->> (rp/cmd! :create-team (with-meta params (meta it))) (rx/tap on-success) (rx/map team-created) @@ -272,11 +272,11 @@ [{:keys [name emails role] :as params}] (ptk/reify ::create-team-with-invitations ptk/WatchEvent - (watch [it state _] + (watch [it _ _] (let [{:keys [on-success on-error] :or {on-success identity on-error rx/throw}} (meta params) - features (features/get-enabled-features state) + features features/global-enabled-features params {:name name :emails emails :role role diff --git a/frontend/src/app/main/data/viewer.cljs b/frontend/src/app/main/data/viewer.cljs index d3baca131..dbd96a290 100644 --- a/frontend/src/app/main/data/viewer.cljs +++ b/frontend/src/app/main/data/viewer.cljs @@ -184,7 +184,7 @@ ptk/UpdateEvent (update [_ state] (let [team-id (:id team) - team {:members users}] + team (assoc team :members users)] (-> state (assoc :share-links share-links) (assoc :current-team-id team-id) diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index 589c431ce..b73695c67 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -205,30 +205,29 @@ (d/index-by :id)))))) (defn- fetch-libraries - [file-id] + [file-id features] (ptk/reify ::fetch-libries ptk/WatchEvent - (watch [_ state _] - (let [features (features/get-team-enabled-features state)] - (->> (rp/cmd! :get-file-libraries {:file-id file-id}) - (rx/mapcat - (fn [libraries] - (rx/concat - (rx/of (libraries-fetched file-id libraries)) - (rx/merge - (->> (rx/from libraries) - (rx/merge-map - (fn [{:keys [id synced-at]}] - (->> (rp/cmd! :get-file {:id id :features features}) - (rx/map #(assoc % :synced-at synced-at :library-of file-id))))) - (rx/mapcat resolve-file) - (rx/map library-resolved)) - (->> (rx/from libraries) - (rx/map :id) - (rx/mapcat (fn [file-id] - (rp/cmd! :get-file-object-thumbnails {:file-id file-id :tag "component"}))) - (rx/map dwl/library-thumbnails-fetched))) - (rx/of (check-libraries-synchronozation file-id libraries)))))))))) + (watch [_ _ _] + (->> (rp/cmd! :get-file-libraries {:file-id file-id}) + (rx/mapcat + (fn [libraries] + (rx/concat + (rx/of (libraries-fetched file-id libraries)) + (rx/merge + (->> (rx/from libraries) + (rx/merge-map + (fn [{:keys [id synced-at]}] + (->> (rp/cmd! :get-file {:id id :features features}) + (rx/map #(assoc % :synced-at synced-at :library-of file-id))))) + (rx/mapcat resolve-file) + (rx/map library-resolved)) + (->> (rx/from libraries) + (rx/map :id) + (rx/mapcat (fn [file-id] + (rp/cmd! :get-file-object-thumbnails {:file-id file-id :tag "component"}))) + (rx/map dwl/library-thumbnails-fetched))) + (rx/of (check-libraries-synchronozation file-id libraries))))))))) (defn- workspace-initialized [file-id] @@ -246,28 +245,16 @@ (fbs/fix-broken-shapes))))) (defn- bundle-fetched - [{:keys [features file thumbnails]}] + [{:keys [file file-id thumbnails] :as bundle}] (ptk/reify ::bundle-fetched IDeref - (-deref [_] - {:features features - :file file - :thumbnails thumbnails}) + (-deref [_] bundle) ptk/UpdateEvent (update [_ state] - (let [file-id (:id file)] - (-> state - (assoc :thumbnails thumbnails) - (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)))))) + (-> state + (assoc :thumbnails thumbnails) + (update :files assoc file-id file))))) (defn zoom-to-frame [] @@ -296,47 +283,30 @@ (defn- fetch-bundle "Multi-stage file bundle fetch coordinator" - [file-id] + [file-id features] (ptk/reify ::fetch-bundle ptk/WatchEvent - (watch [_ state stream] - (let [features (features/get-team-enabled-features state) - render-wasm? (contains? features "render-wasm/v1") - stopper-s (rx/filter (ptk/type? ::finalize-workspace) stream) - team-id (:current-team-id state)] - - (->> (rx/concat - ;; Firstly load wasm module if it is enabled and fonts - (rx/merge - (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 (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))) + (watch [_ _ stream] + (let [stopper-s (rx/filter (ptk/type? ::finalize-workspace) stream)] + (->> (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 + :file-id file-id + :features features + :thumbnails thumbnails}))))) + (rx/map bundle-fetched) (rx/take-until stopper-s)))))) (defn initialize-workspace [team-id file-id] (assert (uuid? team-id) "expected valud uuid for `team-id`") (assert (uuid? file-id) "expected valud uuid for `file-id`") + (ptk/reify ::initialize-workspace ptk/UpdateEvent (update [_ state] @@ -348,24 +318,51 @@ ptk/WatchEvent (watch [_ state stream] - (log/debug :hint "initialize-workspace" :file-id (dm/str file-id)) - (let [stoper-s (rx/filter (ptk/type? ::finalize-workspace) stream) - rparams (rt/get-params state)] + (let [stoper-s (rx/filter (ptk/type? ::finalize-workspace) stream) + 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/of (ntf/hide) - (dcmt/retrieve-comment-threads file-id) - (dcmt/fetch-profiles) - (fetch-bundle file-id)) + (rx/concat + ;; Fetch all essential data that should be loaded before the file + (rx/merge + (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 (rx/filter (ptk/type? ::bundle-fetched)) (rx/take 1) (rx/map deref) - (rx/mapcat (fn [{:keys [file]}] - (rx/of (dpj/initialize-project (:project-id file)) - (-> (workspace-initialized file-id) - (with-meta {:file-id file-id})))))) + (rx/mapcat + (fn [{:keys [file]}] + (rx/of (dpj/initialize-project (:project-id file)) + (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 (rx/filter (ptk/type? ::dps/persistence-notification)) @@ -431,7 +428,6 @@ ptk/WatchEvent (watch [_ state _] (let [project-id (:current-project-id state)] - (rx/of (dwn/finalize file-id) (dpj/finalize-project project-id) (dwsl/finalize-shape-layout) @@ -452,8 +448,6 @@ ;; Make this event callable through dynamic resolution (defmethod ptk/resolve ::reload-current-file [_ _] (reload-current-file)) - - (def ^:private xf:collect-file-media "Resolve and collect all file media on page objects" (comp (map second) @@ -1419,7 +1413,7 @@ (let [objects (dsh/lookup-page-objects state) selected (->> (dsh/lookup-selected state) (cfh/clean-loops objects)) - features (-> (features/get-team-enabled-features state) + features (-> (get state :features) (set/difference cfeat/frontend-only-features)) file-id (:current-file-id state) @@ -1657,9 +1651,10 @@ objects (dsh/lookup-page-objects state)] (when-let [shape (get objects selected)] (let [props (cts/extract-props shape) - features (-> (features/get-team-enabled-features state) + features (-> (get state :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 :features features @@ -1793,8 +1788,8 @@ (ptk/reify ::paste-transit-shapes ptk/WatchEvent (watch [_ state _] - (let [file-id (:current-file-id state) - features (features/get-team-enabled-features state)] + (let [file-id (:current-file-id state) + features (get state :features)] (when-not (paste-data-valid? pdata) (ex/raise :type :validation @@ -1865,7 +1860,7 @@ (ptk/reify ::paste-transit-props ptk/WatchEvent (watch [_ state _] - (let [features (features/get-team-enabled-features state) + (let [features (get state :features) selected (dsh/lookup-selected state)] (when (paste-data-valid? pdata) diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index dadb2af94..2f73ce76c 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -1398,7 +1398,7 @@ ptk/WatchEvent (watch [_ state _] - (let [features (features/get-team-enabled-features state)] + (let [features (get state :features)] (rx/concat (rx/merge (->> (rp/cmd! :link-file-to-library {:file-id file-id :library-id library-id}) diff --git a/frontend/src/app/main/data/workspace/versions.cljs b/frontend/src/app/main/data/workspace/versions.cljs index f444d29a8..f2ae3bb3a 100644 --- a/frontend/src/app/main/data/workspace/versions.cljs +++ b/frontend/src/app/main/data/workspace/versions.cljs @@ -203,9 +203,8 @@ (ptk/reify ::restore-version-from-plugins ptk/WatchEvent (watch [_ state _] - ;; FIXME: revisit this (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/of (ptk/event ::ev/event {::ev/name "restore-version-plugin"}) ::dwp/force-persist) diff --git a/frontend/src/app/main/features.cljs b/frontend/src/app/main/features.cljs index 16fc4222b..146806c26 100644 --- a/frontend/src/app/main/features.cljs +++ b/frontend/src/app/main/features.cljs @@ -8,12 +8,12 @@ "A thin, frontend centric abstraction layer and collection of helpers for `app.common.features` namespace." (:require + [app.common.data.macros :as dm] [app.common.features :as cfeat] [app.common.logging :as log] [app.config :as cf] [app.main.store :as st] [app.render-wasm :as wasm] - [beicon.v2.core :as rx] [clojure.set :as set] [cuerdas.core :as str] [okulary.core :as l] @@ -26,38 +26,32 @@ (cfeat/get-enabled-features cf/flags)) (defn get-enabled-features - [state] - (-> (get state :features-runtime #{}) - (set/intersection cfeat/no-migration-features) - (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))] + "An explicit lookup of enabled features for the current team" + [state team-id] + (let [team (dm/get-in state [:teams team-id])] (-> global-enabled-features - (set/union runtime-features) - (set/intersection cfeat/no-migration-features) - (set/union team-features)))) - -(def features-ref - (l/derived get-team-enabled-features st/state =)) + (set/union (get state :features-runtime #{})) + (set/difference cfeat/no-migration-features) + (set/union (get team :features))))) (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] - (assert (contains? cfeat/supported-features feature) "not supported feature") - (or (contains? (get state :features-runtime) feature) - (if (contains? cfeat/no-migration-features feature) - (or (contains? global-enabled-features feature) - (contains? (get state :features-team) feature)) - (contains? (get state :features-team state) feature)))) + (assert (contains? cfeat/supported-features feature) "feature not supported") + (let [runtime-features (get state :features-runtime) + enabled-features (get state :features)] + (or (contains? runtime-features feature) + (if (contains? cfeat/no-migration-features 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 "A react hook that checks if feature is currently enabled" [feature] - (assert (contains? cfeat/supported-features feature) "Not supported feature") (let [enabled-features (mf/deref features-ref)] (contains? enabled-features feature))) @@ -71,14 +65,16 @@ ptk/UpdateEvent (update [_ state] (assert (contains? cfeat/supported-features feature) "not supported feature") - (update state :features-runtime (fn [features] - (if (contains? features feature) - (do - (log/trc :hint "feature disabled" :feature feature) - (disj features feature)) - (do - (log/trc :hint "feature enabled" :feature feature) - (conj features feature)))))))) + (-> state + (update :features-runtime (fn [features] + (if (contains? features feature) + (do + (log/trc :hint "feature disabled" :feature feature) + (disj features feature)) + (do + (log/trc :hint "feature enabled" :feature feature) + (conj features feature))))) + (update :features-runtime set/intersection cfeat/no-migration-features))))) (defn enable-feature [feature] @@ -90,46 +86,28 @@ state (do (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 - ([] (initialize #{})) - ([team-features] - (assert (set? team-features) "expected a set of features") - (assert (every? string? team-features) "expected a set of strings") + [features] + (ptk/reify ::initialize + ptk/UpdateEvent + (update [_ state] + (let [features (-> global-enabled-features + (set/union (get state :features-runtime #{})) + (set/union features))] + (assoc state :features features))) - (ptk/reify ::initialize - ptk/UpdateEvent - (update [_ state] - (let [runtime-features (get state :features/runtime #{}) - team-features (into #{} - cfeat/xf-supported-features - team-features)] - (-> state - (assoc :features-runtime runtime-features) - (assoc :features-team team-features)))) + ptk/EffectEvent + (effect [_ state _] + (let [features (get state :features)] + (if (contains? features "render-wasm/v1") + (wasm/initialize true) + (wasm/initialize false)) - ptk/WatchEvent - (watch [_ _ _] - (when *assert* - (->> (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)))))))) + (log/inf :hint "initialized" + :enabled (str/join "," features) + :runtime (str/join "," (:features-runtime state))))))) diff --git a/frontend/src/app/main/ui/dashboard/grid.cljs b/frontend/src/app/main/ui/dashboard/grid.cljs index 2a2bf20f7..fd5804760 100644 --- a/frontend/src/app/main/ui/dashboard/grid.cljs +++ b/frontend/src/app/main/ui/dashboard/grid.cljs @@ -17,7 +17,6 @@ [app.main.data.notifications :as ntf] [app.main.data.project :as dpj] [app.main.data.team :as dtm] - [app.main.features :as features] [app.main.fonts :as fonts] [app.main.rasterizer :as thr] [app.main.refs :as refs] @@ -60,7 +59,7 @@ (->> (wrk/ask! {:cmd :thumbnails/generate-for-file :revn revn :file-id file-id - :features (features/get-team-enabled-features @st/state)}) + :features (get @st/state :features)}) (rx/mapcat (fn [{:keys [fonts] :as result}] (->> (fonts/render-font-styles fonts) (rx/map (fn [styles] diff --git a/frontend/src/app/main/ui/dashboard/import.cljs b/frontend/src/app/main/ui/dashboard/import.cljs index ce6b83afb..dc0afa8ca 100644 --- a/frontend/src/app/main/ui/dashboard/import.cljs +++ b/frontend/src/app/main/ui/dashboard/import.cljs @@ -15,7 +15,6 @@ [app.main.data.modal :as modal] [app.main.data.notifications :as ntf] [app.main.errors :as errors] - [app.main.features :as features] [app.main.store :as st] [app.main.ui.components.file-uploader :refer [file-uploader]] [app.main.ui.ds.product.loader :refer [loader*]] @@ -162,29 +161,32 @@ (defn- analyze-entries [state entries] - (->> (uw/ask-many! - {:cmd :analyze-import - :files entries - :features @features/features-ref}) - (rx/mapcat #(rx/delay emit-delay (rx/of %))) - (rx/filter some?) - (rx/subs! - (fn [message] - (swap! state update-with-analyze-result message))))) + (let [features (get @st/state :features)] + (->> (uw/ask-many! + {:cmd :analyze-import + :files entries + :features features}) + (rx/mapcat #(rx/delay emit-delay (rx/of %))) + (rx/filter some?) + (rx/subs! + (fn [message] + (swap! state update-with-analyze-result message)))))) (defn- import-files [state project-id entries] (st/emit! (ptk/data-event ::ev/event {::ev/name "import-files" :num-files (count entries)})) - (->> (uw/ask-many! - {:cmd :import-files - :project-id project-id - :files entries - :features @features/features-ref}) - (rx/filter (comp uuid? :file-id)) - (rx/subs! - (fn [message] - (swap! state update-entry-status message))))) + + (let [features (get @st/state :features)] + (->> (uw/ask-many! + {:cmd :import-files + :project-id project-id + :files entries + :features features}) + (rx/filter (comp uuid? :file-id)) + (rx/subs! + (fn [message] + (swap! state update-entry-status message)))))) (mf/defc import-entry* {::mf/props :obj diff --git a/frontend/src/app/plugins/file.cljs b/frontend/src/app/plugins/file.cljs index decedbf43..22e545dcc 100644 --- a/frontend/src/app/plugins/file.cljs +++ b/frontend/src/app/plugins/file.cljs @@ -13,7 +13,6 @@ [app.main.data.exports.files :as exports.files] [app.main.data.workspace :as dw] [app.main.data.workspace.versions :as dwv] - [app.main.features :as features] [app.main.repo :as rp] [app.main.store :as st] [app.main.worker :as uw] @@ -237,7 +236,7 @@ :else (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) format (case format "zip" :legacy-zip diff --git a/frontend/src/app/render.cljs b/frontend/src/app/render.cljs index 258c98812..bc1fc3f96 100644 --- a/frontend/src/app/render.cljs +++ b/frontend/src/app/render.cljs @@ -41,7 +41,7 @@ ptk/WatchEvent (watch [_ _ _] - (rx/of (features/initialize (or features #{})))))) + (rx/of (features/initialize features))))) (defn- fetch-team [& {:keys [file-id]}] @@ -98,7 +98,7 @@ (ptk/reify ::fetch-objects-bundle ptk/WatchEvent (watch [_ state _] - (let [features (features/get-team-enabled-features state)] + (let [features (get state :features)] (->> (rx/zip (repo/cmd! :get-font-variants {:file-id file-id :share-id share-id}) (repo/cmd! :get-page {:file-id file-id @@ -237,7 +237,7 @@ (ptk/reify ::fetch-components-bundle ptk/WatchEvent (watch [_ state _] - (let [features (features/get-team-enabled-features state)] + (let [features (get state :features)] (->> (repo/cmd! :get-file {:id file-id :features features}) (rx/map (fn [file] #(assoc % :file file)))))))) @@ -309,7 +309,6 @@ (defn ^:export init [] - (st/emit! (features/initialize)) (init-ui)) (defn reinit diff --git a/frontend/src/debug.cljs b/frontend/src/debug.cljs index ad0491fda..4d635b99b 100644 --- a/frontend/src/debug.cljs +++ b/frontend/src/debug.cljs @@ -27,7 +27,6 @@ [app.main.data.workspace.selection :as dws] [app.main.data.workspace.shortcuts] [app.main.errors :as errors] - [app.main.features :as features] [app.main.repo :as rp] [app.main.store :as st] [app.util.debug :as dbg] @@ -393,7 +392,7 @@ (ptk/reify ::repair-current-file ptk/EffectEvent (effect [_ state _] - (let [features (features/get-team-enabled-features state) + (let [features (:features state) sid (:session-id state) file (dsh/lookup-file state) @@ -430,7 +429,3 @@ (defn ^:export 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"))) diff --git a/frontend/src/features.cljs b/frontend/src/features.cljs index 0fa5ec46b..9c30a8624 100644 --- a/frontend/src/features.cljs +++ b/frontend/src/features.cljs @@ -20,13 +20,12 @@ nil) (defn ^:export get-enabled [] - (clj->js (features/get-enabled-features @st/state))) + (clj->js features/global-enabled-features)) (defn ^:export get-team-enabled [] - (clj->js (features/get-team-enabled-features @st/state))) + (clj->js (get @st/state :features))) (defn ^:export plugins [] (st/emit! (features/enable-feature "plugins/runtime")) (plugins/init-plugins-runtime!) nil) -