diff --git a/CHANGES.md b/CHANGES.md index 2abf70c86..d0525be02 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -65,6 +65,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/src/app/rpc/commands/auth.clj b/backend/src/app/rpc/commands/auth.clj index 17557bf95..9307ebab8 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 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 9b5e1598d..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 @@ -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 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/backend/src/app/srepl/main.clj b/backend/src/app/srepl/main.clj index be5c9e57d..6914ff54d 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/src/app/common/features.cljc b/common/src/app/common/features.cljc index e0fb201e4..8f53a9257 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} @@ -156,7 +155,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! @@ -165,6 +163,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) @@ -174,14 +174,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) @@ -192,63 +184,55 @@ 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))))) + ;; Components v1 is deprecated + (when-not (contains? file-features "components/v2") + (ex/raise :type :restriction + :code :file-in-components-v1 + :hint "components v1 is deprecated")) - ;; Components v1 is deprecated - (when-not (contains? file-features "components/v2") - (ex/raise :type :restriction - :code :file-in-components-v1 - :hint "components v1 is deprecated"))) + (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/docs/user-guide/introduction/shortcuts.njk b/docs/user-guide/introduction/shortcuts.njk index 71db0cbb6..0949dc252 100644 --- a/docs/user-guide/introduction/shortcuts.njk +++ b/docs/user-guide/introduction/shortcuts.njk @@ -848,7 +848,7 @@ title: Shortcuts

View mode

-

The View mode is the area to present and share designs and play the proptotype interactions. More about the View mode.

+

The View mode is the area to present and share designs and play the prototype interactions. More about the View mode.

Generic

diff --git a/frontend/src/app/main/data/common.cljs b/frontend/src/app/main/data/common.cljs index b7ebf2716..b202dfcdb 100644 --- a/frontend/src/app/main/data/common.cljs +++ b/frontend/src/app/main/data/common.cljs @@ -72,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 [_ _ _] @@ -80,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)) @@ -90,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)))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/frontend/src/app/main/data/notifications.cljs b/frontend/src/app/main/data/notifications.cljs index 37caee0ba..5c0082bec 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/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index 5d1a96665..727da3790 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -305,7 +305,8 @@ (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 @@ -321,8 +322,7 @@ (watch [_ state stream] (let [stoper-s (rx/filter (ptk/type? ::finalize-workspace) stream) rparams (rt/get-params state) - team-id (get state :current-team-id) - features (get state :features) + features (features/get-enabled-features state team-id) render-wasm? (contains? features "render-wasm/v1")] (log/debug :hint "initialize-workspace" @@ -417,7 +417,7 @@ (unchecked-set ug/global "name" name))))) (defn finalize-workspace - [file-id] + [_team-id file-id] (ptk/reify ::finalize-workspace ptk/UpdateEvent (update [_ state] @@ -450,8 +450,9 @@ (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)) @@ -492,18 +493,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 diff --git a/frontend/src/app/main/data/workspace/notifications.cljs b/frontend/src/app/main/data/workspace/notifications.cljs index e62985f70..dc46e6e35 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 ee710883a..f2ae3bb3a 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 49612ff48..146806c26 100644 --- a/frontend/src/app/main/features.cljs +++ b/frontend/src/app/main/features.cljs @@ -8,6 +8,7 @@ "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] @@ -24,6 +25,15 @@ (def global-enabled-features (cfeat/get-enabled-features cf/flags)) +(defn get-enabled-features + "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 (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." [state feature] diff --git a/frontend/src/app/main/ui.cljs b/frontend/src/app/main/ui.cljs index f75123239..f67fb755a 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/ds/foundations/assets/icon.mdx b/frontend/src/app/main/ui/ds/foundations/assets/icon.mdx index c1184acd5..2f2806c49 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 9284b5387..550d2a297 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 5f19792d6..e78ac7494 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 71adaf819..0dde3c0a4 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} @@ -207,8 +242,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 055e423a3..d29a7ce8e 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/util/websocket.cljs b/frontend/src/app/util/websocket.cljs index 016eb9c0a..5c82ce194 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 213d20ae2..b07cd1fdc 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 38beeb28d..207073f7b 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)