mirror of
https://github.com/penpot/penpot.git
synced 2026-06-09 08:52:05 +00:00
🎉 Enable render switch and wasm info by default and simplify feature helpers to use pre-computed features set (#9942)
The setup-wasm-features function is the single source of truth for
resolving the renderer choice (URL param > profile preference > team
flags), storing the result in state[:features]. Several helpers were
re-deriving the same priority chain independently, duplicating logic:
- wasm-enabled?, wasm-url-override, wasm-url-override-ref
- enabled-by-flags?, enabled-without-migration?
This change removes all duplicated helpers and simplifies the
remaining functions to rely exclusively on the pre-computed
:features set:
- active-feature? — now just checks (contains? (:features state)
feature) without special-casing render-wasm/v1
- use-feature — uses the reactive features-ref for all features
- initialize/recompute-features effects — use the local features
binding directly
Since :features is rebuilt by setup-wasm-features on every
initialization and recompute, this preserves correctness while
eliminating ~50 lines of duplicated code.
This commit is contained in:
parent
2410bcb0df
commit
88f2366c6f
@ -193,7 +193,9 @@
|
||||
:enable-inspect-styles
|
||||
:enable-feature-fdata-objects-map
|
||||
:enable-feature-render-wasm
|
||||
:enable-token-import-from-library])
|
||||
:enable-token-import-from-library
|
||||
:enable-render-switch
|
||||
:enable-render-wasm-info])
|
||||
|
||||
(defn parse
|
||||
[& flags]
|
||||
|
||||
@ -0,0 +1,24 @@
|
||||
{
|
||||
"~:email": "foo@example.com",
|
||||
"~:is-demo": false,
|
||||
"~:auth-backend": "penpot",
|
||||
"~:fullname": "Princesa Leia",
|
||||
"~:modified-at": "~m1713533116365",
|
||||
"~:is-active": true,
|
||||
"~:default-project-id": "~uc7ce0794-0992-8105-8004-38e630f7920b",
|
||||
"~:id": "~uc7ce0794-0992-8105-8004-38e630f29a9b",
|
||||
"~:is-muted": false,
|
||||
"~:default-team-id": "~uc7ce0794-0992-8105-8004-38e630f40f6d",
|
||||
"~:created-at": "~m1713533116365",
|
||||
"~:is-blocked": false,
|
||||
"~:props": {
|
||||
"~:nudge": {
|
||||
"~:big": 10,
|
||||
"~:small": 1
|
||||
},
|
||||
"~:v2-info-shown": true,
|
||||
"~:viewed-tutorial?": false,
|
||||
"~:viewed-walkthrough?": false,
|
||||
"~:renderer": "~:wasm"
|
||||
}
|
||||
}
|
||||
@ -1,16 +1,21 @@
|
||||
import { expect } from "@playwright/test";
|
||||
import { WorkspacePage } from "./WorkspacePage";
|
||||
|
||||
export const WASM_PROFILE = "logged-in-user/get-profile-wasm-renderer.json";
|
||||
|
||||
export const WASM_FLAGS = [
|
||||
"enable-feature-render-wasm",
|
||||
"enable-render-wasm-dpr",
|
||||
"enable-feature-text-editor-v2",
|
||||
// Default flags enable render-wasm-info; keep screenshots stable in e2e.
|
||||
"disable-render-wasm-info",
|
||||
];
|
||||
|
||||
export class WasmWorkspacePage extends WorkspacePage {
|
||||
static async init(page) {
|
||||
await super.init(page);
|
||||
await WasmWorkspacePage.mockConfigFlags(page, WASM_FLAGS);
|
||||
await WasmWorkspacePage.mockRPC(page, "get-profile", WASM_PROFILE);
|
||||
|
||||
await page.addInitScript(() => {
|
||||
document.addEventListener("penpot:wasm:loaded", () => {
|
||||
@ -99,4 +104,9 @@ export class WasmWorkspacePage extends WorkspacePage {
|
||||
options,
|
||||
);
|
||||
}
|
||||
|
||||
async setupEmptyFile() {
|
||||
await super.setupEmptyFile();
|
||||
await this.mockRPC("get-profile", WASM_PROFILE);
|
||||
}
|
||||
}
|
||||
|
||||
@ -28,19 +28,42 @@
|
||||
|
||||
(defn setup-wasm-features
|
||||
[features state]
|
||||
(let [params (rt/get-params state)
|
||||
wasm (get params :wasm)
|
||||
renderer (when (contains? cf/flags :render-switch)
|
||||
(-> state :profile :props :renderer))
|
||||
enable-wasm (or (= "true" wasm) (and (= renderer :wasm) (not= "false" wasm)))
|
||||
disable-wasm (or (= "false" wasm) (and (= renderer :svg) (not= "true" wasm)))
|
||||
features (cond-> features
|
||||
enable-wasm (conj "render-wasm/v1")
|
||||
disable-wasm (disj "render-wasm/v1"))]
|
||||
;; If wasm render is enabled text-editor/v2 must be used
|
||||
(cond-> features
|
||||
(contains? features "render-wasm/v1")
|
||||
(conj "text-editor/v2"))))
|
||||
(let [wasm-override (-> (rt/get-params state)
|
||||
(get :wasm))
|
||||
|
||||
;; When the :render-switch feature flag is active, read the user's
|
||||
;; preferred renderer from their profile settings (default :svg).
|
||||
renderer (when (contains? cf/flags :render-switch)
|
||||
(dm/get-in state [:profile :props :renderer] :svg))
|
||||
|
||||
features (cond
|
||||
;; Priority 1 — URL query param (?wasm=true|false)
|
||||
;; overrides everything: user profile and team flags.
|
||||
(= "true" wasm-override)
|
||||
(conj features "render-wasm/v1")
|
||||
|
||||
(= "false" wasm-override)
|
||||
(disj features "render-wasm/v1")
|
||||
|
||||
;; Priority 2 — User profile preference.
|
||||
;; If renderer is non-nil, the :render-switch flag is
|
||||
;; active and profile data has loaded. Respect the
|
||||
;; user's saved choice (:wasm or :svg).
|
||||
(some? renderer)
|
||||
(if (= :wasm renderer)
|
||||
(conj features "render-wasm/v1")
|
||||
(disj features "render-wasm/v1"))
|
||||
|
||||
;; Priority 3 — Fall back to the team-level
|
||||
;; feature set unchanged (no override).
|
||||
:else
|
||||
features)]
|
||||
|
||||
;; The WASM renderer requires the v2 text editor (hard dependency).
|
||||
;; Ensure it's always enabled whenever render-wasm/v1 is active.
|
||||
(if (contains? features "render-wasm/v1")
|
||||
(conj features "text-editor/v2")
|
||||
(disj features "text-editor/v2"))))
|
||||
|
||||
(defn get-enabled-features
|
||||
"An explicit lookup of enabled features for the current team"
|
||||
@ -52,65 +75,15 @@
|
||||
(set/union (get team :features))
|
||||
(setup-wasm-features state))))
|
||||
|
||||
(defn enabled-by-flags?
|
||||
[{:keys [features-runtime features]} feature]
|
||||
(or (contains? features-runtime feature)
|
||||
(contains? features feature)))
|
||||
|
||||
(defn enabled-without-migration?
|
||||
[{:keys [features-runtime features]} feature]
|
||||
(or (contains? features-runtime feature)
|
||||
(contains? global-enabled-features feature)
|
||||
(contains? features feature)))
|
||||
|
||||
(defn wasm-url-override
|
||||
[state]
|
||||
(case (get (rt/get-params state) :wasm)
|
||||
"true" true
|
||||
"false" false
|
||||
nil))
|
||||
|
||||
(def wasm-url-override-ref
|
||||
(l/derived wasm-url-override st/state))
|
||||
|
||||
(defn- wasm-enabled?
|
||||
[state]
|
||||
(let [override (wasm-url-override state)
|
||||
renderer (when (contains? cf/flags :render-switch)
|
||||
(-> state :profile :props :renderer))]
|
||||
(cond
|
||||
(some? override)
|
||||
override
|
||||
|
||||
(contains? cf/flags :render-switch)
|
||||
(case renderer
|
||||
:wasm true
|
||||
:svg false
|
||||
;; SVG renderer as default until profile data arrives OR if render-switch
|
||||
;; flag is disabled.
|
||||
false)
|
||||
|
||||
(contains? cfeat/no-migration-features "render-wasm/v1")
|
||||
(enabled-without-migration? state "render-wasm/v1")
|
||||
|
||||
:else
|
||||
(enabled-by-flags? state "render-wasm/v1"))))
|
||||
|
||||
(defn active-feature?
|
||||
"Given a state and feature, check if feature is enabled."
|
||||
"Given a state and feature, check if feature is enabled.
|
||||
Relies on the pre-computed :features set in state, which already
|
||||
incorporates URL overrides, user profile preferences, team flags,
|
||||
and runtime toggles via setup-wasm-features."
|
||||
[state feature]
|
||||
(assert (contains? cfeat/supported-features feature)
|
||||
"feature not supported")
|
||||
|
||||
(cond
|
||||
(= feature "render-wasm/v1")
|
||||
(wasm-enabled? state)
|
||||
|
||||
(contains? cfeat/no-migration-features feature)
|
||||
(enabled-without-migration? state feature)
|
||||
|
||||
:else
|
||||
(enabled-by-flags? state feature)))
|
||||
(contains? (:features state) feature))
|
||||
|
||||
(defn active-features?
|
||||
"Given a state and a set of features, check if the features are all enabled."
|
||||
@ -134,26 +107,12 @@
|
||||
(l/derived (l/key :features) st/state))
|
||||
|
||||
(defn use-feature
|
||||
"A react hook that checks if feature is currently enabled"
|
||||
"A react hook that checks if feature is currently enabled.
|
||||
Uses the pre-computed :features set from state — the same set that
|
||||
setup-wasm-features has already resolved."
|
||||
[feature]
|
||||
(let [enabled-features (mf/deref features-ref)
|
||||
wasm-override (mf/deref wasm-url-override-ref)
|
||||
renderer (mf/deref (l/derived #(-> % :profile :props :renderer) st/state))
|
||||
wasm-enabled (cond
|
||||
(some? wasm-override)
|
||||
wasm-override
|
||||
|
||||
(contains? cf/flags :render-switch)
|
||||
(= renderer :wasm)
|
||||
|
||||
:else
|
||||
(contains? enabled-features "render-wasm/v1"))]
|
||||
(cond
|
||||
(= feature "render-wasm/v1")
|
||||
wasm-enabled
|
||||
|
||||
:else
|
||||
(contains? enabled-features feature))))
|
||||
(let [enabled-features (mf/deref features-ref)]
|
||||
(contains? enabled-features feature)))
|
||||
|
||||
(defn toggle-feature
|
||||
"An event constructor for runtime feature toggle.
|
||||
@ -204,7 +163,7 @@
|
||||
ptk/EffectEvent
|
||||
(effect [_ state _]
|
||||
(let [features (get state :features)]
|
||||
(if (active-feature? state "render-wasm/v1")
|
||||
(if (contains? features "render-wasm/v1")
|
||||
(wasm/initialize true)
|
||||
(wasm/initialize false))
|
||||
|
||||
@ -225,7 +184,7 @@
|
||||
ptk/EffectEvent
|
||||
(effect [_ state _]
|
||||
(let [features (get state :features)]
|
||||
(if (active-feature? state "render-wasm/v1")
|
||||
(if (contains? features "render-wasm/v1")
|
||||
(wasm/initialize true)
|
||||
(wasm/initialize false))
|
||||
|
||||
|
||||
@ -12,6 +12,7 @@
|
||||
[app.config :as cf]
|
||||
[app.main.data.team :as dtm]
|
||||
[app.main.errors :as errors]
|
||||
[app.main.features :as features]
|
||||
[app.main.repo :as rp]
|
||||
[app.main.router :as rt]
|
||||
[app.main.store :as st]
|
||||
@ -147,6 +148,14 @@
|
||||
[]
|
||||
(ptk/reify ::init-routes
|
||||
ptk/WatchEvent
|
||||
(watch [_ _ _]
|
||||
(rx/of (rt/initialize-router routes)
|
||||
(rt/initialize-history on-navigate)))))
|
||||
(watch [_ _ stream]
|
||||
(rx/merge
|
||||
(rx/of (rt/initialize-router routes)
|
||||
(rt/initialize-history on-navigate))
|
||||
(->> stream
|
||||
(rx/filter (ptk/type? ::rt/navigated))
|
||||
(rx/map deref)
|
||||
(rx/map #(dm/get-in % [:query-params :wasm]))
|
||||
(rx/buffer 2 1)
|
||||
(rx/filter (fn [[v1 v2]] (not= v1 v2)))
|
||||
(rx/map features/recompute-features))))))
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
[app.common.schema :as sm]
|
||||
[app.common.test-helpers.files :as cthf]
|
||||
[app.main.data.workspace.layout :as layout]
|
||||
[app.main.features :as features]
|
||||
[beicon.v2.core :as rx]
|
||||
[potok.v2.core :as ptk]))
|
||||
|
||||
@ -18,7 +19,10 @@
|
||||
:workspace-global layout/default-global
|
||||
:current-file-id nil
|
||||
:current-page-id nil
|
||||
:features-team #{"components/v2"}})
|
||||
:features-team #{"components/v2"}
|
||||
;; With :render-switch enabled by default, render-wasm/v1 follows the
|
||||
;; profile renderer preference instead of the global feature flag alone.
|
||||
:profile {:props {:renderer :wasm}}})
|
||||
|
||||
(defn- on-error
|
||||
[cause]
|
||||
@ -34,6 +38,9 @@
|
||||
:permissions {:can-edit true}
|
||||
:files {(:id file) file}))
|
||||
store (ptk/store {:state state :on-error on-error})]
|
||||
;; Unit tests skip team/workspace bootstrap; mirror team init so
|
||||
;; :features is populated the same way as features/initialize does in app.
|
||||
(ptk/emit! store (features/initialize #{}))
|
||||
store))
|
||||
|
||||
(defn run-store
|
||||
|
||||
@ -8,8 +8,9 @@
|
||||
"Test helpers for mocking WASM API boundary functions.
|
||||
|
||||
In the Node.js test environment the WASM binary is not available,
|
||||
but the `render-wasm/v1` feature flag is enabled by default, so
|
||||
every geometry-modifying event takes the WASM code path.
|
||||
but `render-wasm/v1` is active when the test store uses a profile
|
||||
with `:renderer :wasm` (see `frontend-tests.helpers.state`), so
|
||||
geometry/text events take the WASM code path.
|
||||
This namespace provides lightweight mock implementations that let
|
||||
the Clojure-side logic execute normally while stubbing out every
|
||||
call that would touch the WASM heap.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user