diff --git a/CHANGES.md b/CHANGES.md index 0ea7662b6b..066ac40127 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,6 @@ # CHANGELOG -## 2.6.0 (Unreleased) +## 2.6.0 ### :rocket: Epics and highlights @@ -25,6 +25,7 @@ - [DESIGN TOKENS] Import and export tokens from a JSON file. - [DESIGN TOKENS] Apply Themes and Sets at document level. - Add more descriptive tooltip to boards for first time users [Taiga #9426](https://tree.taiga.io/project/penpot/us/9426) +- First State of a Project Changes Consolidation [Taia #10605](https://tree.taiga.io/project/penpot/us/10605) ### :bug: Bugs fixed @@ -47,6 +48,7 @@ - Fix available size of resize handler [Taiga #10639](https://tree.taiga.io/project/penpot/issue/10639) - Internal error when install a plugin by penpothub - Try plugin [Taiga #10542](https://tree.taiga.io/project/penpot/issue/10542) - Add character limitation to asset inputs [Taiga #10669](https://tree.taiga.io/project/penpot/issue/10669) +- Fix Storybook link 'list of all available icons' wrong path [Taiga #10705](https://tree.taiga.io/project/penpot/issue/10705) ## 2.5.4 diff --git a/backend/package.json b/backend/package.json index c0981d82c0..126166f4cc 100644 --- a/backend/package.json +++ b/backend/package.json @@ -4,7 +4,7 @@ "license": "MPL-2.0", "author": "Kaleidos INC", "private": true, - "packageManager": "yarn@4.6.0+sha512.5383cc12567a95f1d668fbe762dfe0075c595b4bfff433be478dbbe24e05251a8e8c3eb992a986667c1d53b6c3a9c85b8398c35a960587fbd9fa3a0915406728", + "packageManager": "yarn@4.8.1+sha512.bc946f2a022d7a1a38adfc15b36a66a3807a67629789496c3714dd1703d2e6c6b1c69ff9ec3b43141ac7a1dd853b7685638eb0074300386a59c18df351ef8ff6", "repository": { "type": "git", "url": "https://github.com/penpot/penpot" diff --git a/backend/src/app/http/errors.clj b/backend/src/app/http/errors.clj index 9647ecec29..eaeb9eef7e 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/auth.clj b/backend/src/app/rpc/commands/auth.clj index 17557bf95d..9307ebab8c 100644 --- a/backend/src/app/rpc/commands/auth.clj +++ b/backend/src/app/rpc/commands/auth.clj @@ -55,7 +55,7 @@ (contains? cf/flags :login-with-password)) (ex/raise :type :restriction :code :login-disabled - :hint "login is disabled in this instance")) + :hint "login is disabled")) (letfn [(check-password [cfg profile password] (if (= (:password profile) "!") @@ -79,7 +79,8 @@ :code :wrong-credentials)) (when (:is-blocked profile) (ex/raise :type :restriction - :code :profile-blocked)) + :code :profile-blocked + :hint "profile is marked as blocked")) (when-not (check-password cfg profile password) (ex/raise :type :validation :code :wrong-credentials)) @@ -183,11 +184,11 @@ (defn- validate-register-attempt! [cfg params] - (when (or - (not (contains? cf/flags :registration)) - (not (contains? cf/flags :login-with-password))) + (when (or (not (contains? cf/flags :registration)) + (not (contains? cf/flags :login-with-password))) (ex/raise :type :restriction - :code :registration-disabled)) + :code :registration-disabled + :hint "registration disabled")) (when (contains? params :invitation-token) (let [invitation (tokens/verify (::setup/props cfg) @@ -201,12 +202,14 @@ (when (and (email.blacklist/enabled? cfg) (email.blacklist/contains? cfg (:email params))) (ex/raise :type :restriction - :code :email-domain-is-not-allowed)) + :code :email-domain-is-not-allowed + :hint "email domain in blacklist")) (when (and (email.whitelist/enabled? cfg) (not (email.whitelist/contains? cfg (:email params)))) (ex/raise :type :restriction - :code :email-domain-is-not-allowed)) + :code :email-domain-is-not-allowed + :hint "email domain not in whitelist")) ;; Perform a basic validation of email & password (when (= (str/lower (:email params)) @@ -219,13 +222,13 @@ (ex/raise :type :restriction :code :email-has-permanent-bounces :email (:email params) - :hint "looks like the email has bounce reports")) + :hint "email has bounce reports")) (when (eml/has-complaint-reports? cfg (:email params)) (ex/raise :type :restriction :code :email-has-complaints :email (:email params) - :hint "looks like the email has complaint reports"))) + :hint "email has complaint reports"))) (defn prepare-register [{:keys [::db/pool] :as cfg} {:keys [email] :as params}] diff --git a/backend/src/app/rpc/commands/files.clj b/backend/src/app/rpc/commands/files.clj index f409b0bc74..39b3d6c029 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 9b5e1598de..0502ff4872 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 @@ -120,7 +117,7 @@ ;; to lost team features updating ;; When newly computed features does not match exactly with - ;; the features defined on team row, we update it. + ;; the features defined on team row, we update it (when (not= features (:features team)) (let [features (db/create-array conn "text" features)] (db/update! conn :team diff --git a/backend/src/app/rpc/commands/files_thumbnails.clj b/backend/src/app/rpc/commands/files_thumbnails.clj index c3a5cb5d47..f4c5e3d140 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 396f3ffbd2..73b4afd3d8 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/backend/src/app/srepl/main.clj b/backend/src/app/srepl/main.clj index be5c9e57db..6914ff54dd 100644 --- a/backend/src/app/srepl/main.clj +++ b/backend/src/app/srepl/main.clj @@ -209,100 +209,116 @@ This method allows send flash notifications to specified target destinations. The message can be a free text or a preconfigured one. - The destination can be: all, profile-id, team-id, or a coll of them." - [{:keys [::mbus/msgbus ::db/pool]} & {:keys [dest code message level] - :or {code :generic level :info} - :as params}] + The destination can be: all, profile-id, team-id, or a coll of them. + It also can be: + + {:email \"some@example.com\"} + [[:email \"some@example.com\"], ...] + + Command examples: + + (notify! :dest :all :code :maintenance) + (notify! :dest :all :code :upgrade-version) + " + [& {:keys [dest code message level] + :or {code :generic level :info} + :as params}] (when-not (contains? #{:success :error :info :warning} level) (ex/raise :type :assertion :code :incorrect-level :hint (str "level '" level "' not supported"))) - (letfn [(send [dest] - (l/inf :hint "sending notification" :dest (str dest)) - (let [message {:type :notification - :code code - :level level - :version (:full cf/version) - :subs-id dest - :message message} - message (->> (dissoc params :dest :code :message :level) - (merge message))] - (mbus/pub! msgbus - :topic (str dest) - :message message))) + (let [{:keys [::mbus/msgbus ::db/pool]} main/system - (resolve-profile [email] - (some-> (db/get* pool :profile {:email (str/lower email)} {:columns [:id]}) :id vector)) + send + (fn [dest] + (l/inf :hint "sending notification" :dest (str dest)) + (let [message {:type :notification + :code code + :level level + :version (:full cf/version) + :subs-id dest + :message message} + message (->> (dissoc params :dest :code :message :level) + (merge message))] + (mbus/pub! msgbus + :topic dest + :message message))) - (resolve-team [team-id] - (->> (db/query pool :team-profile-rel - {:team-id team-id} - {:columns [:profile-id]}) - (map :profile-id))) + resolve-profile + (fn [email] + (some-> (db/get* pool :profile {:email (str/lower email)} {:columns [:id]}) :id vector)) - (resolve-dest [dest] - (cond - (= :all dest) - [uuid/zero] + resolve-team + (fn [team-id] + (->> (db/query pool :team-profile-rel + {:team-id team-id} + {:columns [:profile-id]}) + (map :profile-id))) - (uuid? dest) - [dest] + resolve-dest + (fn resolve-dest [dest] + (cond + (= :all dest) + [uuid/zero] - (string? dest) - (some-> dest h/parse-uuid resolve-dest) + (uuid? dest) + [dest] - (nil? dest) - (resolve-dest uuid/zero) + (string? dest) + (some-> dest h/parse-uuid resolve-dest) - (map? dest) - (sequence (comp - (map vec) - (mapcat resolve-dest)) - dest) + (nil? dest) + [uuid/zero] - (and (vector? dest) - (every? vector? dest)) - (sequence (comp - (map vec) - (mapcat resolve-dest)) - dest) + (map? dest) + (sequence (comp + (map vec) + (mapcat resolve-dest)) + dest) - (and (vector? dest) - (keyword? (first dest))) - (let [[op param] dest] + (and (vector? dest) + (every? vector? dest)) + (sequence (comp + (map vec) + (mapcat resolve-dest)) + dest) + + (and (vector? dest) + (keyword? (first dest))) + (let [[op param] dest] + (cond + (= op :email) (cond - (= op :email) - (cond - (and (coll? param) - (every? string? param)) - (sequence (comp - (keep resolve-profile) - (mapcat identity)) - param) + (and (coll? param) + (every? string? param)) + (sequence (comp + (keep resolve-profile) + (mapcat identity)) + param) - (string? param) - (resolve-profile param)) + (string? param) + (resolve-profile param)) - (= op :team-id) - (cond - (coll? param) - (sequence (comp - (mapcat resolve-team) - (keep h/parse-uuid)) - param) + (= op :team-id) + (cond + (coll? param) + (sequence (comp + (mapcat resolve-team) + (keep h/parse-uuid)) + param) - (uuid? param) - (resolve-team param) + (uuid? param) + (resolve-team param) - (string? param) - (some-> param h/parse-uuid resolve-team)) + (string? param) + (some-> param h/parse-uuid resolve-team)) - (= op :profile-id) - (if (coll? param) - (sequence (keep h/parse-uuid) param) - (resolve-dest param))))))] + (= op :profile-id) + (if (coll? param) + (sequence (keep h/parse-uuid) param) + (resolve-dest param))))))] (->> (resolve-dest dest) (filter some?) diff --git a/common/package.json b/common/package.json index faf1e14d24..5a449d1286 100644 --- a/common/package.json +++ b/common/package.json @@ -4,7 +4,7 @@ "license": "MPL-2.0", "author": "Kaleidos INC", "private": true, - "packageManager": "yarn@4.6.0+sha512.5383cc12567a95f1d668fbe762dfe0075c595b4bfff433be478dbbe24e05251a8e8c3eb992a986667c1d53b6c3a9c85b8398c35a960587fbd9fa3a0915406728", + "packageManager": "yarn@4.8.1+sha512.bc946f2a022d7a1a38adfc15b36a66a3807a67629789496c3714dd1703d2e6c6b1c69ff9ec3b43141ac7a1dd853b7685638eb0074300386a59c18df351ef8ff6", "type": "module", "repository": { "type": "git", diff --git a/common/src/app/common/features.cljc b/common/src/app/common/features.cljc index b396e19370..a56829ef33 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/exporter/package.json b/exporter/package.json index ed9a0c9b70..7ab00e7701 100644 --- a/exporter/package.json +++ b/exporter/package.json @@ -4,7 +4,7 @@ "license": "MPL-2.0", "author": "Kaleidos INC", "private": true, - "packageManager": "yarn@4.7.0+sha512.5a0afa1d4c1d844b3447ee3319633797bcd6385d9a44be07993ae52ff4facabccafb4af5dcd1c2f9a94ac113e5e9ff56f6130431905884414229e284e37bb7c9", + "packageManager": "yarn@4.8.1+sha512.bc946f2a022d7a1a38adfc15b36a66a3807a67629789496c3714dd1703d2e6c6b1c69ff9ec3b43141ac7a1dd853b7685638eb0074300386a59c18df351ef8ff6", "repository": { "type": "git", "url": "https://github.com/penpot/penpot" diff --git a/frontend/package.json b/frontend/package.json index 094e5cfc71..ba740928eb 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -4,7 +4,7 @@ "license": "MPL-2.0", "author": "Kaleidos INC", "private": true, - "packageManager": "yarn@4.7.0+sha512.5a0afa1d4c1d844b3447ee3319633797bcd6385d9a44be07993ae52ff4facabccafb4af5dcd1c2f9a94ac113e5e9ff56f6130431905884414229e284e37bb7c9", + "packageManager": "yarn@4.8.1+sha512.bc946f2a022d7a1a38adfc15b36a66a3807a67629789496c3714dd1703d2e6c6b1c69ff9ec3b43141ac7a1dd853b7685638eb0074300386a59c18df351ef8ff6", "browserslist": [ "defaults" ], diff --git a/frontend/src/app/main.cljs b/frontend/src/app/main.cljs index e36985b178..b13c47dd83 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 72de74ea9f..c63832693e 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 064d1901d5..b202dfcdb1 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] @@ -73,7 +72,7 @@ (st/emit! (ntf/hide))) (defn handle-notification - [{:keys [message code level] :as params}] + [{:keys [message code] :as params}] (ptk/reify ::show-notification ptk/WatchEvent (watch [_ _ _] @@ -81,9 +80,6 @@ :upgrade-version (rx/of (ntf/dialog :content (tr "notifications.by-code.upgrade-version") - :controls :inline-actions - :type :inline - :level level :accept {:label (tr "labels.refresh") :callback force-reload!} :tag :notification)) @@ -91,16 +87,14 @@ :maintenance (rx/of (ntf/dialog :content (tr "notifications.by-code.maintenance") - :controls :inline-actions - :type level :accept {:label (tr "labels.accept") :callback hide-notifications!} :tag :notification)) (rx/of (ntf/dialog :content message - :controls :close - :type level + :accept {:label (tr "labels.close") + :callback hide-notifications!} :tag :notification)))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -112,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)] @@ -169,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 57a886340a..4aedd2ad77 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] @@ -57,8 +56,7 @@ (rx/filter (fn [{:keys [topic] :as msg}] (or (= topic uuid/zero) (= topic profile-id)))) - (rx/map process-message) - (rx/ignore))) + (rx/map process-message))) (rx/take-until stopper)))))) @@ -497,7 +495,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 56ab281a70..b89c027fe3 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/notifications.cljs b/frontend/src/app/main/data/notifications.cljs index 37caee0ba2..5c0082bec3 100644 --- a/frontend/src/app/main/data/notifications.cljs +++ b/frontend/src/app/main/data/notifications.cljs @@ -19,7 +19,7 @@ (def ^:private schema:notification [:map {:title "Notification"} - [:level [::sm/one-of #{:success :error :info :warning}]] + [:level {:optional true} [::sm/one-of #{:success :error :info :warning}]] [:status {:optional true} [::sm/one-of #{:visible :hide}]] [:position {:optional true} @@ -129,15 +129,11 @@ :timeout timeout}))) (defn dialog - [& {:keys [content controls actions accept cancel position tag level links] - :or {controls :none position :floating level :info}}] + [& {:keys [content accept cancel tag links]}] (show (d/without-nils {:content content - :level level - :links links - :position position - :controls controls - :actions actions + :type :inline :accept accept :cancel cancel + :links links :tag tag}))) diff --git a/frontend/src/app/main/data/team.cljs b/frontend/src/app/main/data/team.cljs index 2ee1e5828e..ce96d45b25 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 d3baca1317..dbd96a2900 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 9939108aa8..b73695c67c 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,46 +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 - [file-id] + [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] @@ -347,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)) @@ -410,7 +408,7 @@ (unchecked-set ug/global "name" name))))) (defn finalize-workspace - [file-id] + [_team-id file-id] (ptk/reify ::finalize-workspace ptk/UpdateEvent (update [_ state] @@ -430,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) @@ -444,14 +441,13 @@ (ptk/reify ::reload-current-file ptk/WatchEvent (watch [_ state _] - (let [file-id (:current-file-id state)] - (rx/of (initialize-workspace file-id)))))) + (let [file-id (:current-file-id state) + team-id (:current-team-id state)] + (rx/of (initialize-workspace team-id file-id)))))) ;; 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) @@ -488,18 +484,25 @@ (defn initialize-page [file-id page-id] (assert (uuid? file-id) "expected valid uuid for `file-id`") + (assert (uuid? page-id) "expected valid uuid for `page-id`") (ptk/reify ::initialize-page ptk/WatchEvent (watch [_ state _] (if-let [page (dsh/lookup-page state file-id page-id)] - (rx/concat (rx/of (initialize-page* file-id page-id page) - (dwth/watch-state-changes file-id page-id) - (dwl/watch-component-changes)) - (let [profile (:profile state) - props (get profile :props)] - (when (not (:workspace-visited props)) - (rx/of (select-frame-tool file-id page-id))))) + (rx/concat + (rx/of (initialize-page* file-id page-id page) + (dwth/watch-state-changes file-id page-id) + (dwl/watch-component-changes)) + (let [profile (:profile state) + props (get profile :props)] + (when (not (:workspace-visited props)) + (rx/of (select-frame-tool file-id page-id))))) + + ;; NOTE: this redirect is necessary for cases where user + ;; explicitly passes an non-existing page-id on the url + ;; params, so on check it we can detect that there are no data + ;; for the page and redirect user to an existing page (rx/of (dcm/go-to-workspace :file-id file-id ::rt/replace true)))))) (defn finalize-page @@ -1410,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) @@ -1648,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 @@ -1784,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 @@ -1856,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 dadb2af945..2f73ce76cc 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/notifications.cljs b/frontend/src/app/main/data/workspace/notifications.cljs index e62985f70b..dc46e6e355 100644 --- a/frontend/src/app/main/data/workspace/notifications.cljs +++ b/frontend/src/app/main/data/workspace/notifications.cljs @@ -65,7 +65,6 @@ (->> (rx/from initmsg) (rx/map dws/send)) - ;; Subscribe to notifications of the subscription (->> stream (rx/filter (ptk/type? ::dws/message)) diff --git a/frontend/src/app/main/data/workspace/versions.cljs b/frontend/src/app/main/data/workspace/versions.cljs index ee710883aa..f2ae3bb3a7 100644 --- a/frontend/src/app/main/data/workspace/versions.cljs +++ b/frontend/src/app/main/data/workspace/versions.cljs @@ -10,6 +10,7 @@ [app.common.data.macros :as dm] [app.common.schema :as sm] [app.main.data.event :as ev] + [app.main.data.helpers :as dsh] [app.main.data.persistence :as dwp] [app.main.data.workspace :as dw] [app.main.data.workspace.thumbnails :as th] @@ -97,7 +98,8 @@ (ptk/reify ::restore-version ptk/WatchEvent (watch [_ state _] - (let [file-id (:current-file-id state)] + (let [file-id (:current-file-id state) + team-id (:current-team-id state)] (rx/concat (rx/of ::dwp/force-persist (dw/remove-layout-flag :document-history)) @@ -106,7 +108,7 @@ (rx/take 1) (rx/mapcat #(rp/cmd! :restore-file-snapshot {:file-id file-id :id id})) (rx/tap #(th/clear-queue!)) - (rx/map #(dw/initialize-workspace file-id))) + (rx/map #(dw/initialize-workspace team-id file-id))) (case origin :version (rx/of (ptk/event ::ev/event {::ev/name "restore-pin-version"})) @@ -200,21 +202,23 @@ (ptk/reify ::restore-version-from-plugins ptk/WatchEvent - (watch [_ _ _] - (rx/concat - (rx/of (ptk/event ::ev/event {::ev/name "restore-version-plugin"}) - ::dwp/force-persist) + (watch [_ state _] + (let [file (dsh/lookup-file state file-id) + 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) - ;; FIXME: we should abstract this - (->> (rx/from-atom refs/persistence-state {:emit-current-value? true}) - (rx/filter #(or (nil? %) (= :saved %))) - (rx/take 1) - (rx/mapcat #(rp/cmd! :restore-file-snapshot {:file-id file-id :id id})) - (rx/map #(dw/initialize-workspace file-id))) + ;; FIXME: we should abstract this + (->> (rx/from-atom refs/persistence-state {:emit-current-value? true}) + (rx/filter #(or (nil? %) (= :saved %))) + (rx/take 1) + (rx/mapcat #(rp/cmd! :restore-file-snapshot {:file-id file-id :id id})) + (rx/map #(dw/initialize-workspace team-id file-id))) - (->> (rx/of 1) - (rx/tap resolve) - (rx/ignore)))))) + (->> (rx/of 1) + (rx/tap resolve) + (rx/ignore))))))) diff --git a/frontend/src/app/main/features.cljs b/frontend/src/app/main/features.cljs index 16fc4222bc..23deb766fc 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/union (get state :features-runtime #{})) (set/intersection cfeat/no-migration-features) - (set/union team-features)))) - -(def features-ref - (l/derived get-team-enabled-features st/state =)) + (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.cljs b/frontend/src/app/main/ui.cljs index f751232397..f67fb755a3 100644 --- a/frontend/src/app/main/ui.cljs +++ b/frontend/src/app/main/ui.cljs @@ -370,6 +370,6 @@ (if edata [:> static/exception-page* {:data edata :route route}] [:> error-boundary* {:fallback static/internal-error*} - [:& notifications/current-notification] + [:> notifications/current-notification*] (when route [:> page* {:route route :profile profile}])])]])) diff --git a/frontend/src/app/main/ui/dashboard/grid.cljs b/frontend/src/app/main/ui/dashboard/grid.cljs index 2a2bf20f76..fd5804760b 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 ce6b83afb4..dc0afa8ca0 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/main/ui/dashboard/placeholder.cljs b/frontend/src/app/main/ui/dashboard/placeholder.cljs index 042c0815a5..ce6177dacc 100644 --- a/frontend/src/app/main/ui/dashboard/placeholder.cljs +++ b/frontend/src/app/main/ui/dashboard/placeholder.cljs @@ -7,8 +7,8 @@ (ns app.main.ui.dashboard.placeholder (:require-macros [app.main.style :as stl]) (:require - [app.config :as cf] [app.main.data.event :as ev] + [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.dashboard.import :as udi] [app.main.ui.ds.product.empty-placeholder :refer [empty-placeholder*]] @@ -55,9 +55,10 @@ (mf/deps create-fn) (fn [_] (create-fn "dashboard:empty-folder-placeholder"))) - show-text (mf/use-state nil) + show-text (mf/use-state nil) on-mouse-enter (mf/use-fn #(reset! show-text true)) - on-mouse-leave (mf/use-fn #(reset! show-text nil))] + on-mouse-leave (mf/use-fn #(reset! show-text nil)) + files (mf/deref refs/files)] (cond (true? dragging?) [:ul @@ -79,18 +80,14 @@ :tag-name "span"}])] :else - (if (cf/external-feature-flag "add-file-02" "test") + (if (= (count files) 0) [:> empty-placeholder-projects* {:on-create on-click :on-finish-import on-finish-import :project-id project-id}] [:div {:class (stl/css :grid-empty-placeholder)} - (if (cf/external-feature-flag "add-file-01" "test") - [:button {:class (stl/css :create-new) - :on-click on-click - :on-mouse-enter on-mouse-enter - :on-mouse-leave on-mouse-leave} - (if @show-text (tr "dashboard.empty-project.create") i/add)] - [:button {:class (stl/css :create-new) - :on-click on-click} - i/add])])))) + [:button {:class (stl/css :create-new) + :on-click on-click + :on-mouse-enter on-mouse-enter + :on-mouse-leave on-mouse-leave} + (if @show-text (tr "dashboard.empty-project.create") i/add)]])))) (mf/defc loading-placeholder [] diff --git a/frontend/src/app/main/ui/ds/foundations/assets/icon.mdx b/frontend/src/app/main/ui/ds/foundations/assets/icon.mdx index c1184acd51..2f2806c494 100644 --- a/frontend/src/app/main/ui/ds/foundations/assets/icon.mdx +++ b/frontend/src/app/main/ui/ds/foundations/assets/icon.mdx @@ -5,7 +5,7 @@ import * as IconStories from "./icon.stories" # Iconography -See the [list of all available icons](?path=/story/foundations-icons--all-icons). +See the [list of all available icons](?path=/story/foundations-assets-icon--all). ## Variants diff --git a/frontend/src/app/main/ui/ds/notifications/actionable.cljs b/frontend/src/app/main/ui/ds/notifications/actionable.cljs index 9284b5387a..550d2a2976 100644 --- a/frontend/src/app/main/ui/ds/notifications/actionable.cljs +++ b/frontend/src/app/main/ui/ds/notifications/actionable.cljs @@ -17,10 +17,10 @@ [:class {:optional true} :string] [:variant {:optional true} [:maybe [:enum "default" "error"]]] - [:accept-label {:optional true} :string] - [:cancel-label {:optional true} :string] - [:on-accept {:optional true} [:fn fn?]] - [:on-cancel {:optional true} [:fn fn?]]]) + [:accept-label {:optional true} [:maybe :string]] + [:cancel-label {:optional true} [:maybe :string]] + [:on-accept {:optional true} [:maybe [:fn fn?]]] + [:on-cancel {:optional true} [:maybe [:fn fn?]]]]) (mf/defc actionable* {::mf/schema schema:actionable} @@ -45,9 +45,13 @@ [:> :aside props [:div {:class (stl/css :notification-message)} children] - [:> button* {:variant "secondary" - :on-click on-cancel} - cancel-label] - [:> button* {:variant (if (= variant "default") "primary" "destructive") - :on-click on-accept} - accept-label]])) + + (when cancel-label + [:> button* {:variant "secondary" + :on-click on-cancel} + cancel-label]) + + (when accept-label + [:> button* {:variant (if (= variant "default") "primary" "destructive") + :on-click on-accept} + accept-label])])) diff --git a/frontend/src/app/main/ui/notifications.cljs b/frontend/src/app/main/ui/notifications.cljs index 5f19792d64..e78ac74944 100644 --- a/frontend/src/app/main/ui/notifications.cljs +++ b/frontend/src/app/main/ui/notifications.cljs @@ -14,10 +14,10 @@ [okulary.core :as l] [rumext.v2 :as mf])) -(def ref:notification +(def ^:private ref:notification (l/derived :notification st/state)) -(mf/defc current-notification +(mf/defc current-notification* [] (let [notification (mf/deref ref:notification) on-close (mf/use-fn #(st/emit! (ntf/hide))) diff --git a/frontend/src/app/main/ui/workspace.cljs b/frontend/src/app/main/ui/workspace.cljs index d6de04f207..83349799a5 100644 --- a/frontend/src/app/main/ui/workspace.cljs +++ b/frontend/src/app/main/ui/workspace.cljs @@ -9,6 +9,7 @@ (:require [app.common.data.macros :as dm] [app.main.data.common :as dcm] + [app.main.data.helpers :as dsh] [app.main.data.persistence :as dps] [app.main.data.plugins :as dpl] [app.main.data.workspace :as dw] @@ -45,9 +46,10 @@ (mf/defc workspace-content* {::mf/private true} [{:keys [file layout page wglobal]}] + (let [palete-size (mf/use-state nil) selected (mf/deref refs/selected-shapes) - page-id (:id page) + page-id (get page :id) {:keys [vport] :as wlocal} (mf/deref refs/workspace-local) {:keys [options-mode]} wglobal @@ -120,10 +122,46 @@ :overlay true :file-loading true}]) +(defn- make-team-ref + [team-id] + (l/derived (fn [state] + (let [teams (get state :teams)] + (get teams team-id))) + st/state)) + +(defn- make-file-ref + [file-id] + (l/derived (fn [state] + ;; NOTE: for ensure ordering of execution, we need to + ;; wait the file initialization completly success until + ;; mark this file availablea and unlock the rendering + ;; of the following components + (when (= (get state :current-file-id) file-id) + (let [files (get state :files) + file (get files file-id)] + (-> file + (dissoc :data) + (assoc ::has-data (contains? file :data)))))) + st/state)) + +(defn- make-page-ref + [file-id page-id] + (l/derived (fn [state] + (let [current-page-id (get state :current-page-id)] + ;; NOTE: for ensure ordering of execution, we need to + ;; wait the page initialization completly success until + ;; mark this file availablea and unlock the rendering + ;; of the following components + (when (= current-page-id page-id) + (dsh/lookup-page state file-id page-id)))) + st/state)) + (mf/defc workspace-page* {::mf/private true} [{:keys [page-id file-id file layout wglobal]}] - (let [page (mf/deref refs/workspace-page)] + (let [page-ref (mf/with-memo [file-id page-id] + (make-page-ref file-id page-id)) + page (mf/deref page-ref)] (mf/with-effect [] (let [focus-out #(st/emit! (dw/workspace-focus-lost)) @@ -133,8 +171,7 @@ (mf/with-effect [file-id page-id] (st/emit! (dw/initialize-page file-id page-id)) (fn [] - (when page-id - (st/emit! (dw/finalize-page file-id page-id))))) + (st/emit! (dw/finalize-page file-id page-id)))) (if (some? page) [:> workspace-content* {:file file @@ -143,18 +180,9 @@ :layout layout}] [:> workspace-loader*]))) -(def ^:private ref:file-without-data - (l/derived (fn [file] - (-> file - (dissoc :data) - (assoc ::has-data (contains? file :data)))) - refs/file - =)) - (mf/defc workspace* - {::mf/props :obj - ::mf/wrap [mf/memo]} - [{:keys [project-id file-id page-id layout-name]}] + {::mf/wrap [mf/memo]} + [{:keys [team-id project-id file-id page-id layout-name]}] (let [file-id (hooks/use-equal-memo file-id) page-id (hooks/use-equal-memo page-id) @@ -162,8 +190,15 @@ layout (mf/deref refs/workspace-layout) wglobal (mf/deref refs/workspace-global) - team (mf/deref refs/team) - file (mf/deref ref:file-without-data) + team-ref (mf/with-memo [team-id] + (make-team-ref team-id)) + file-ref (mf/with-memo [file-id] + (make-file-ref file-id)) + + team (mf/deref team-ref) + file (mf/deref file-ref) + + file-loaded? (get file ::has-data) file-name (:name file) permissions (:permissions team) @@ -187,14 +222,14 @@ (when file-name (dom/set-html-title (tr "title.workspace" file-name)))) - (mf/with-effect [file-id] - (st/emit! (dw/initialize-workspace file-id)) + (mf/with-effect [team-id file-id] + (st/emit! (dw/initialize-workspace team-id file-id)) (fn [] (st/emit! ::dps/force-persist - (dw/finalize-workspace file-id)))) + (dw/finalize-workspace team-id file-id)))) - (mf/with-effect [file page-id] - (when-not page-id + (mf/with-effect [file-id page-id file-loaded?] + (when (and file-loaded? (not page-id)) (st/emit! (dcm/go-to-workspace :file-id file-id ::rt/replace true)))) [:> (mf/provider ctx/current-project-id) {:value project-id} @@ -208,8 +243,7 @@ :style {:background-color background-color :touch-action "none"}} [:> context-menu*] - - (if (::has-data file) + (if (and file-loaded? page-id) [:> workspace-page* {:page-id page-id :file-id file-id diff --git a/frontend/src/app/main/worker.cljs b/frontend/src/app/main/worker.cljs index 055e423a34..d29a7ce8e1 100644 --- a/frontend/src/app/main/worker.cljs +++ b/frontend/src/app/main/worker.cljs @@ -17,8 +17,10 @@ [] (let [worker (uw/init cf/worker-uri err/on-error)] (uw/ask! worker {:cmd :configure - :key :public-uri - :val cf/public-uri}) + :config {:public-uri cf/public-uri + :build-data cf/build-date + :version cf/version}}) + (set! instance worker))) (defn ask! diff --git a/frontend/src/app/plugins/file.cljs b/frontend/src/app/plugins/file.cljs index decedbf43d..22e545dcc5 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 258c988128..bc1fc3f967 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/app/util/websocket.cljs b/frontend/src/app/util/websocket.cljs index 016eb9c0ae..5c82ce1948 100644 --- a/frontend/src/app/util/websocket.cljs +++ b/frontend/src/app/util/websocket.cljs @@ -104,7 +104,9 @@ (defn send! [ws msg] - (-send ws (t/encode-str msg))) + (if *assert* + (-send ws (t/encode-str msg {:type :json-verbose})) + (-send ws (t/encode-str msg)))) (defn close! [ws] diff --git a/frontend/src/app/worker.cljs b/frontend/src/app/worker.cljs index 213d20ae2b..b07cd1fdc4 100644 --- a/frontend/src/app/worker.cljs +++ b/frontend/src/app/worker.cljs @@ -135,15 +135,15 @@ (rx/debounce 1) (rx/subs! (fn [[messages dropped last]] - ;; Send back the dropped messages replies + ;; Send back the dropped messages replies (doseq [msg dropped] (drop-message msg)) - ;; Process the message + ;; Process the message (doseq [msg (vals messages)] (handle-message msg)) - ;; After process the buffer we send a clear + ;; After process the buffer we send a clear (when-not (= last ::clear) (rx/push! buffer ::clear))))))) diff --git a/frontend/src/app/worker/impl.cljs b/frontend/src/app/worker/impl.cljs index 38beeb28d4..207073f7bc 100644 --- a/frontend/src/app/worker/impl.cljs +++ b/frontend/src/app/worker/impl.cljs @@ -50,8 +50,16 @@ (handler (assoc message :cmd :snaps/update-page-index)))) (defmethod handler :configure - [{:keys [key val]}] - (log/info :hint "configure worker" :key key :val (dm/str val)) - (case key - :public-uri - (set! cf/public-uri val))) + [{:keys [config]}] + (log/info :hint "configure worker" :keys (keys config)) + + (when-let [public-uri (get config :public-uri)] + (set! cf/public-uri public-uri)) + + (when-let [version (get config :version)] + (set! cf/version version)) + + (when-let [build-date (get config :build-data)] + (set! cf/build-date build-date)) + + nil) diff --git a/frontend/src/debug.cljs b/frontend/src/debug.cljs index ad0491fda0..4d635b99b5 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 0fa5ec46ba..9c30a86244 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) - diff --git a/package.json b/package.json index 0a560c87a5..b3e2845bc9 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "license": "MPL-2.0", "author": "Kaleidos INC", "private": true, - "packageManager": "yarn@4.2.2", + "packageManager": "yarn@4.8.1+sha512.bc946f2a022d7a1a38adfc15b36a66a3807a67629789496c3714dd1703d2e6c6b1c69ff9ec3b43141ac7a1dd853b7685638eb0074300386a59c18df351ef8ff6", "repository": { "type": "git", "url": "https://github.com/penpot/penpot"