From 6a5538bb156345351b31d5d360551fc00a6500cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mar=C3=ADa=20Valderrama?= Date: Tue, 18 Feb 2025 11:32:00 +0100 Subject: [PATCH 1/6] :bug: Fix unreachable Save color style button (#5879) * :bug: Fix unreachable Save color style button * :paperclip: Fix unreachable Save color style button code review --- .../app/main/ui/workspace/colorpicker.cljs | 209 +++++++++--------- .../app/main/ui/workspace/colorpicker.scss | 4 + 2 files changed, 113 insertions(+), 100 deletions(-) diff --git a/frontend/src/app/main/ui/workspace/colorpicker.cljs b/frontend/src/app/main/ui/workspace/colorpicker.cljs index c982d8b4c7..e6d9fced39 100644 --- a/frontend/src/app/main/ui/workspace/colorpicker.cljs +++ b/frontend/src/app/main/ui/workspace/colorpicker.cljs @@ -403,104 +403,104 @@ :h h :s s :v v :alpha (/ alpha 255)})))) - [:div {:class (stl/css :colorpicker) - :ref node-ref - :style {:touch-action "none"}} - [:div {:class (stl/css :top-actions)} - [:div {:class (stl/css :top-actions-right)} - (when (= :gradient selected-mode) - [:div {:class (stl/css :opacity-input-wrapper)} - [:span {:class (stl/css :icon-text)} "%"] - [:> numeric-input* - {:value (-> data :opacity opacity->string) - :on-change handle-change-gradient-opacity - :default 100 - :min 0 - :max 100}]]) + [:* + [:div {:class (stl/css :colorpicker) + :ref node-ref + :style {:touch-action "none"}} + [:div {:class (stl/css :top-actions)} + [:div {:class (stl/css :top-actions-right)} + (when (= :gradient selected-mode) + [:div {:class (stl/css :opacity-input-wrapper)} + [:span {:class (stl/css :icon-text)} "%"] + [:> numeric-input* + {:value (-> data :opacity opacity->string) + :on-change handle-change-gradient-opacity + :default 100 + :min 0 + :max 100}]]) - (when (or (not disable-gradient) (not disable-image)) - [:div {:class (stl/css :select)} - [:& select - {:default-value selected-mode - :options options - :on-change handle-change-mode}]])] + (when (or (not disable-gradient) (not disable-image)) + [:div {:class (stl/css :select)} + [:& select + {:default-value selected-mode + :options options + :on-change handle-change-mode}]])] - (when (not= selected-mode :image) - [:button {:class (stl/css-case :picker-btn true - :selected picking-color?) - :on-click handle-click-picker} - i/picker])] + (when (not= selected-mode :image) + [:button {:class (stl/css-case :picker-btn true + :selected picking-color?) + :on-click handle-click-picker} + i/picker])] - (when (= selected-mode :gradient) - [:> gradients* - {:type (:type state) - :stops (:stops state) - :editing-stop (:editing-stop state) - :on-stop-edit-start handle-stop-edit-start - :on-stop-edit-finish handle-stop-edit-finish - :on-select-stop handle-change-gradient-selected-stop - :on-change-type handle-change-gradient-type - :on-change-stop handle-gradient-change-stop - :on-add-stop-auto handle-gradient-add-stop-auto - :on-add-stop-preview handle-gradient-add-stop-preview - :on-remove-stop handle-gradient-remove-stop - :on-rotate-stops handle-rotate-stops - :on-reverse-stops handle-reverse-stops - :on-reorder-stops handle-reorder-stops}]) + (when (= selected-mode :gradient) + [:> gradients* + {:type (:type state) + :stops (:stops state) + :editing-stop (:editing-stop state) + :on-stop-edit-start handle-stop-edit-start + :on-stop-edit-finish handle-stop-edit-finish + :on-select-stop handle-change-gradient-selected-stop + :on-change-type handle-change-gradient-type + :on-change-stop handle-gradient-change-stop + :on-add-stop-auto handle-gradient-add-stop-auto + :on-add-stop-preview handle-gradient-add-stop-preview + :on-remove-stop handle-gradient-remove-stop + :on-rotate-stops handle-rotate-stops + :on-reverse-stops handle-reverse-stops + :on-reorder-stops handle-reorder-stops}]) - (if (= selected-mode :image) - (let [uri (cfg/resolve-file-media (:image current-color)) - keep-aspect-ratio? (-> current-color :image :keep-aspect-ratio)] - [:div {:class (stl/css :select-image)} - [:div {:class (stl/css :content)} - (when (:image current-color) - [:img {:src uri}])] + (if (= selected-mode :image) + (let [uri (cfg/resolve-file-media (:image current-color)) + keep-aspect-ratio? (-> current-color :image :keep-aspect-ratio)] + [:div {:class (stl/css :select-image)} + [:div {:class (stl/css :content)} + (when (:image current-color) + [:img {:src uri}])] - (when (some? (:image current-color)) - [:div {:class (stl/css :checkbox-option)} - [:label {:for "keep-aspect-ratio" - :class (stl/css-case :global/checked keep-aspect-ratio?)} - [:span {:class (stl/css-case :global/checked keep-aspect-ratio?)} - (when keep-aspect-ratio? - i/status-tick)] - (tr "media.keep-aspect-ratio") - [:input {:type "checkbox" - :id "keep-aspect-ratio" - :checked keep-aspect-ratio? - :on-change handle-change-keep-aspect-ratio}]]]) - [:button - {:class (stl/css :choose-image) - :title (tr "media.choose-image") - :aria-label (tr "media.choose-image") - :on-click on-fill-image-click} - (tr "media.choose-image") - [:& file-uploader - {:input-id "fill-image-upload" - :accept "image/jpeg,image/png" - :multi false - :ref fill-image-ref - :on-selected on-fill-image-selected}]]]) - [:* - [:div {:class (stl/css :colorpicker-tabs)} - [:> tab-switcher* {:tabs tabs - :default-selected "ramp" - :on-change-tab on-change-tab}]] + (when (some? (:image current-color)) + [:div {:class (stl/css :checkbox-option)} + [:label {:for "keep-aspect-ratio" + :class (stl/css-case :global/checked keep-aspect-ratio?)} + [:span {:class (stl/css-case :global/checked keep-aspect-ratio?)} + (when keep-aspect-ratio? + i/status-tick)] + (tr "media.keep-aspect-ratio") + [:input {:type "checkbox" + :id "keep-aspect-ratio" + :checked keep-aspect-ratio? + :on-change handle-change-keep-aspect-ratio}]]]) + [:button + {:class (stl/css :choose-image) + :title (tr "media.choose-image") + :aria-label (tr "media.choose-image") + :on-click on-fill-image-click} + (tr "media.choose-image") + [:& file-uploader + {:input-id "fill-image-upload" + :accept "image/jpeg,image/png" + :multi false + :ref fill-image-ref + :on-selected on-fill-image-selected}]]]) + [:* + [:div {:class (stl/css :colorpicker-tabs)} + [:> tab-switcher* {:tabs tabs + :default-selected "ramp" + :on-change-tab on-change-tab}]] - [:& color-inputs - {:type type - :disable-opacity disable-opacity - :color current-color - :on-change handle-change-color}] - - [:& libraries - {:state state - :current-color current-color - :disable-gradient disable-gradient - :disable-opacity disable-opacity - :disable-image disable-image - :on-select-color on-select-library-color - :on-add-library-color on-add-library-color}]]) + [:& color-inputs + {:type type + :disable-opacity disable-opacity + :color current-color + :on-change handle-change-color}] + [:& libraries + {:state state + :current-color current-color + :disable-gradient disable-gradient + :disable-opacity disable-opacity + :disable-image disable-image + :on-select-color on-select-library-color + :on-add-library-color on-add-library-color}]])] (when (fn? on-accept) [:div {:class (stl/css :actions)} [:button {:class (stl/css-case @@ -520,32 +520,41 @@ max-y (- vh h) rulers? (mf/deref refs/rulers?) left-offset (if rulers? 40 18) - right-offset (+ w 40)] - + right-offset (+ w 40) + top-offset (dm/str (- y 70) "px") + bottom-offset "1rem" + max-height-top (str "calc(100vh - " top-offset) + max-height-bottom (str "calc(100vh -" bottom-offset)] (cond (or (nil? x) (nil? y)) - #js {:left "auto" :right "16rem" :top "4rem"} + #js {:left "auto" :right "16rem" :top "4rem" :maxHeight "calc(100vh - 4rem)"} (= position :left) (if (> y max-y) #js {:left (dm/str (- x right-offset) "px") - :bottom "1rem"} + :bottom bottom-offset + :maxHeight max-height-bottom} #js {:left (dm/str (- x right-offset) "px") - :top (dm/str (- y 70) "px")}) + :top top-offset + :maxHeight max-height-top}) (= position :right) (if (> y max-y) #js {:left (dm/str (+ x 80) "px") - :bottom "1rem"} + :bottom bottom-offset + :maxHeight max-height-bottom} #js {:left (dm/str (+ x 80) "px") - :top (dm/str (- y 70) "px")}) + :top top-offset + :maxHeight max-height-top}) :else (if (> y max-y) #js {:left (dm/str (+ x left-offset) "px") - :bottom "1rem"} + :bottom bottom-offset + :maxHeight max-height-bottom} #js {:left (dm/str (+ x left-offset) "px") - :top (dm/str (- y 70) "px")})))) + :top top-offset + :maxHeight max-height-top})))) (mf/defc colorpicker-modal {::mf/register modal/components diff --git a/frontend/src/app/main/ui/workspace/colorpicker.scss b/frontend/src/app/main/ui/workspace/colorpicker.scss index b4595bf841..09b27b7464 100644 --- a/frontend/src/app/main/ui/workspace/colorpicker.scss +++ b/frontend/src/app/main/ui/workspace/colorpicker.scss @@ -13,10 +13,14 @@ width: auto; padding: var(--sp-m); width: $sz-284; + overflow: auto; + display: flex; + flex-direction: column; } .colorpicker { border-radius: $br-8; + overflow: auto; } .colorpicker-tabs { From 807b8d82e3369df625935bf14f0a533a818c96b2 Mon Sep 17 00:00:00 2001 From: Yamila Moreno Date: Tue, 18 Feb 2025 12:36:16 +0100 Subject: [PATCH 2/6] :wrench: Improve flags documentation (#5863) * :paperclip: Fix typo * :wrench: Enable certain flags by default * :wrench: Compile all flags in a single source of truth * :paperclip: Move all default flags to common --------- Co-authored-by: Andrey Antukh --- backend/src/app/config.clj | 12 +-- common/src/app/common/features.cljc | 2 +- common/src/app/common/flags.cljc | 136 +++++++++++++++++++++++++++- frontend/src/app/config.cljs | 8 +- 4 files changed, 137 insertions(+), 21 deletions(-) diff --git a/backend/src/app/config.clj b/backend/src/app/config.clj index 6e9f31b313..1d3dac9a50 100644 --- a/backend/src/app/config.clj +++ b/backend/src/app/config.clj @@ -228,19 +228,9 @@ [:objects-storage-s3-endpoint {:optional true} ::sm/uri] [:objects-storage-s3-io-threads {:optional true} ::sm/int]])) -(def default-flags - [:enable-backend-api-doc - :enable-backend-openapi-doc - :enable-backend-worker - :enable-secure-session-cookies - :enable-email-verification - :enable-v2-migration]) - (defn- parse-flags [config] - (flags/parse flags/default - default-flags - (:flags config))) + (flags/parse flags/default (:flags config))) (defn read-env [prefix] diff --git a/common/src/app/common/features.cljc b/common/src/app/common/features.cljc index 0ced5b1d8f..8389f8be66 100644 --- a/common/src/app/common/features.cljc +++ b/common/src/app/common/features.cljc @@ -141,7 +141,7 @@ (keep flag->feature)) (defn get-enabled-features - "Get the globally enabled fratures set." + "Get the globally enabled features set." [flags] (into default-features xf-flag-to-feature flags)) diff --git a/common/src/app/common/flags.cljc b/common/src/app/common/flags.cljc index 791be1f529..cba12a9cde 100644 --- a/common/src/app/common/flags.cljc +++ b/common/src/app/common/flags.cljc @@ -7,13 +7,145 @@ (ns app.common.flags "Flags parsing algorithm." (:require + [clojure.set :as set] [cuerdas.core :as str])) +(def login + "Flags related to login features" + #{;; Allows registration with login / password + ;; if disabled, it's still possible to register/login with providers + :registration + ;; Redundant flag. TODO: remove it + :login + ;; enables the section of Access Tokens on profile. + :access-tokens + ;; Uses email and password as credentials. + :login-with-password + ;; Uses Github authentication as credentials. + :login-with-github + ;; Uses GitLab authentication as credentials. + :login-with-gitlab + ;; Uses Google/Gmail authentication as credentials. + :login-with-google + ;; Uses LDAP authentication as credentials. + :login-with-ldap + ;; Uses any generic authentication provider that implements OIDC protocol as credentials. + :login-with-oidc + ;; Allows registration with Open ID + :oidc-registration + ;; This logs to console the invitation tokens. It's useful in case the SMTP is not configured. + :log-invitation-tokens}) + +(def email + "Flags related to email features" + #{;; Uses the domains in whitelist as the only allowed domains to register in the application. + ;; Used with PENPOT_REGISTRATION_DOMAIN_WHITELIST + :email-whitelist + ;; Prevents the domains in blacklist to register in the application. + ;; Used with PENPOT_REGISTRATION_DOMAIN_BLACKLIST + :email-blacklist + ;; Skips the email verification process. Not recommended for production environments. + :email-verification + ;; Only used if SMTP is disabled. Logs the emails into the console. + :log-emails + ;; Enable it to configure email settings. + :smtp + ;; Enables the debug mode of the SMTP library. + :smtp-debug}) + +(def varia + "Rest of the flags" + #{:audit-log + :audit-log-archive + :audit-log-gc + :auto-file-snapshot + ;; enables the `/api/doc` endpoint that lists all the rpc methods available. + :backend-api-doc + ;; TODO: remove it and use only `backend-api-doc` flag + :backend-openapi-doc + ;; Disable it to start the RPC without the worker. + :backend-worker + ;; Only for development + :component-thumbnails + ;; enables the default cors configuration that allows all domains (currently this configuration is only used for development). + :cors + ;; Enables the templates dialog on Penpot dashboard. + :dashboard-templates-section + ;; disabled by default. When enabled, Penpot create demo users with a 7 days expiration. + :demo-users + ;; disabled by default. When enabled, it displays a warning that this is a test instance and data will be deleted periodically. + :demo-warning + ;; Activates the schema validation during update file. + :file-schema-validation + ;; Reports the schema validation errors internally. + :soft-file-schema-validation + ;; Activates the referential integrity validation during update file; related to components-v2. + :file-validation + ;; Reports the referential integrity validation errors internally. + :soft-file-validation + ;; TODO: deprecate this flag and consolidate the code + :frontend-svgo + ;; TODO: deprecate this flag and consolidate the code + :exporter-svgo + ;; TODO: deprecate this flag and consolidate the code + :backend-svgo + ;; If enabled, it makes the Google Fonts available. + :google-fonts-provider + ;; Only for development. + :nrepl-server + ;; Interactive repl. Only for development. + :urepl-server + ;; Programatic access to the runtime, used in administrative tasks. + ;; It's mandatory to enable it to use the `manage.py` script. + :prepl-server + ;; Shows the onboarding modals right after registration. + :onboarding + :quotes + :soft-quotes + ;; Concurrency limit. + :rpc-climit + ;; Rate limit. + :rpc-rlimit + ;; Soft rate limit. + :soft-rpc-rlimit + ;; Disable it if you want to serve Penpot under a different domain than `http://localhost` without HTTPS. + :secure-session-cookies + ;; If `cors` enabled, this is ignored. + :strict-session-cookies + :telemetry + :terms-and-privacy-checkbox + ;; Only for developtment. + :tiered-file-data-storage + :transit-readable-response + :user-feedback + ;; TODO: remove this flag. + :v2-migration + :webhooks + ;; TODO: deprecate this flag and consolidate the code + :export-file-v3 + :render-wasm-dpr + :hide-release-modal}) + +(def all-flags + (set/union email login varia)) + (def default - "A common flags that affects both: backend and frontend." + "Flags with default configuration" [:enable-registration + :enable-login-with-password :enable-export-file-v3 - :enable-login-with-password]) + :enable-frontend-svgo + :enable-exporter-svgo + :enable-backend-svgo + :enable-backend-api-doc + :enable-backend-openapi-doc + :enable-backend-worker + :enable-secure-session-cookies + :enable-email-verification + :enable-onboarding + :enable-dashboard-templates-section + :enable-google-fonts-provider + :enable-component-thumbnails]) (defn parse [& flags] diff --git a/frontend/src/app/config.cljs b/frontend/src/app/config.cljs index 3a268027ed..11ff04b2d9 100644 --- a/frontend/src/app/config.cljs +++ b/frontend/src/app/config.cljs @@ -63,17 +63,11 @@ :browser :webworker)) -(def default-flags - [:enable-onboarding - :enable-dashboard-templates-section - :enable-google-fonts-provider - :enable-component-thumbnails]) - (defn- parse-flags [global] (let [flags (obj/get global "penpotFlags" "") flags (sequence (map keyword) (str/words flags))] - (flags/parse flags/default default-flags flags))) + (flags/parse flags/default flags))) (defn- parse-version [global] From fe3fec7a504c00fa63000b230022af01432e1266 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Fri, 14 Feb 2025 15:40:01 +0100 Subject: [PATCH 3/6] :bug: Fix workspace hot reload race condtion This reverts commit 8139ee3ef936498f183818065067ea931ca894a2. --- frontend/src/app/main/data/dashboard.cljs | 9 +- frontend/src/app/main/data/workspace.cljs | 93 +++++++++---------- frontend/src/app/main/router.cljs | 5 + frontend/src/app/main/ui.cljs | 1 - frontend/src/app/main/ui/workspace.cljs | 51 +++++----- .../logic/copying_and_duplicating_test.cljs | 4 +- 6 files changed, 77 insertions(+), 86 deletions(-) diff --git a/frontend/src/app/main/data/dashboard.cljs b/frontend/src/app/main/data/dashboard.cljs index d42f307251..b3ebe6ace8 100644 --- a/frontend/src/app/main/data/dashboard.cljs +++ b/frontend/src/app/main/data/dashboard.cljs @@ -460,10 +460,11 @@ ptk/UpdateEvent (update [_ state] - (-> state - (assoc-in [:files id] file) - (assoc-in [:recent-files id] file) - (update-in [:projects project-id :count] inc))))) + (let [file (dissoc file :data)] + (-> state + (assoc-in [:files id] file) + (assoc-in [:recent-files id] file) + (update-in [:projects project-id :count] inc)))))) (defn create-file [{:keys [project-id name] :as params}] diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index ccffeef9a4..f870bb2541 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -108,9 +108,6 @@ (declare ^:private workspace-initialized) (declare ^:private fetch-libraries) (declare ^:private libraries-fetched) -(declare ^:private preload-data-uris) - -;; (declare go-to-layout) ;; --- Initialize Workspace @@ -314,13 +311,10 @@ (defn initialize-workspace [file-id] (assert (uuid? file-id) "expected valud uuid for `file-id`") - (ptk/reify ::initialize-workspace ptk/UpdateEvent (update [_ state] (-> state - (dissoc :files) - (dissoc :workspace-ready) (assoc :recent-colors (:recent-colors storage/user)) (assoc :recent-fonts (:recent-fonts storage/user)) (assoc :current-file-id file-id) @@ -395,11 +389,9 @@ (dissoc :current-file-id :workspace-editor-state - :files :workspace-media-objects :workspace-persistence :workspace-presence - :workspace-ready :workspace-undo) (update :workspace-global dissoc :read-only?) (assoc-in [:workspace-global :options-mode] :design))) @@ -426,48 +418,67 @@ ;; Make this event callable through dynamic resolution (defmethod ptk/resolve ::reload-current-file [_ _] (reload-current-file)) -(defn initialize-page - [page-id] - (assert (uuid? page-id) "expected valid uuid for `page-id`") - (ptk/reify ::initialize-page + +(def ^:private xf:collect-file-media + "Resolve and collect all file media on page objects" + (comp (map second) + (keep (fn [{:keys [metadata fill-image]}] + (cond + (some? metadata) (cf/resolve-file-media metadata) + (some? fill-image) (cf/resolve-file-media fill-image)))))) + + +(defn- initialize-page* + "Second phase of page initialization, once we know the page is + available on the sate" + [file-id page-id page] + (ptk/reify ::initialize-page* ptk/UpdateEvent (update [_ state] - (if-let [{:keys [id] :as page} (dsh/lookup-page state page-id)] - ;; we maintain a cache of page state for user convenience with the exception of the - ;; selection; when user abandon the current page, the selection is lost - (let [local (dm/get-in state [:workspace-cache id] default-workspace-local)] - (-> state - (assoc :current-page-id id) - (assoc :workspace-local (assoc local :selected (d/ordered-set))) - (assoc :workspace-trimmed-page (dm/select-keys page [:id :name])) + ;; selection; when user abandon the current page, the selection is lost + (let [local (dm/get-in state [:workspace-cache [file-id page-id]] default-workspace-local)] + (-> state + (assoc :current-page-id page-id) + (assoc :workspace-local (assoc local :selected (d/ordered-set))) + (assoc :workspace-trimmed-page (dm/select-keys page [:id :name])) - ;; FIXME: this should be done on `initialize-layout` (?) - (update :workspace-layout layout/load-layout-flags) - (update :workspace-global layout/load-layout-state))) + ;; FIXME: this should be done on `initialize-layout` (?) + (update :workspace-layout layout/load-layout-flags) + (update :workspace-global layout/load-layout-state)))) - state)) + ptk/EffectEvent + (effect [_ _ _] + (let [uris (into #{} xf:collect-file-media (:objects page))] + (->> (rx/from uris) + (rx/subs! #(http/fetch-data-uri % false))))))) +(defn initialize-page + [file-id page-id] + (assert (uuid? file-id) "expected valid uuid for `file-id`") + + (ptk/reify ::initialize-page ptk/WatchEvent (watch [_ state _] - (if (dsh/lookup-page state page-id) - (let [file-id (:current-file-id state)] - (rx/of (preload-data-uris page-id) - (dwth/watch-state-changes file-id page-id) - (dwl/watch-component-changes))) - (rx/of (dcm/go-to-workspace)))))) + (if-let [page (dsh/lookup-page state file-id page-id)] + (rx/of (initialize-page* file-id page-id page) + (dwth/watch-state-changes file-id page-id) + (dwl/watch-component-changes)) + (rx/of (dcm/go-to-workspace :file-id file-id ::rt/replace true)))))) (defn finalize-page - [page-id] + [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 ::finalize-page ptk/UpdateEvent (update [_ state] (let [local (-> (:workspace-local state) (dissoc :edition :edit-path :selected)) - exit? (not= :workspace (dm/get-in state [:route :data :name])) + exit? (not= :workspace (rt/lookup-name state)) state (-> state - (update :workspace-cache assoc page-id local) + (update :workspace-cache assoc [file-id page-id] local) (dissoc :current-page-id :workspace-local :workspace-trimmed-page @@ -476,22 +487,6 @@ (cond-> state exit? (dissoc :workspace-drawing)))))) -(defn- preload-data-uris - "Preloads the image data so it's ready when necessary" - [page-id] - (ptk/reify ::preload-data-uris - ptk/EffectEvent - (effect [_ state _] - (let [xform (comp (map second) - (keep (fn [{:keys [metadata fill-image]}] - (cond - (some? metadata) (cf/resolve-file-media metadata) - (some? fill-image) (cf/resolve-file-media fill-image))))) - uris (into #{} xform (dsh/lookup-page-objects state page-id))] - - (->> (rx/from uris) - (rx/subs! #(http/fetch-data-uri % false))))))) - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Workspace Page CRUD ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/frontend/src/app/main/router.cljs b/frontend/src/app/main/router.cljs index 58e421983a..e4c3b16a1e 100644 --- a/frontend/src/app/main/router.cljs +++ b/frontend/src/app/main/router.cljs @@ -120,6 +120,11 @@ ([id params & {:as options}] (navigate id params options))) +(defn lookup-name + [state] + (dm/get-in state [:route :data :name])) + +;; FIXME: rename to lookup-params (defn get-params [state] (dm/get-in state [:route :params :query])) diff --git a/frontend/src/app/main/ui.cljs b/frontend/src/app/main/ui.cljs index 250e37cf57..8a32539d54 100644 --- a/frontend/src/app/main/ui.cljs +++ b/frontend/src/app/main/ui.cljs @@ -127,7 +127,6 @@ {::mf/props :obj ::mf/private true} [{:keys [team-id children]}] - (mf/with-effect [team-id] (st/emit! (dtm/initialize-team team-id)) (fn [] diff --git a/frontend/src/app/main/ui/workspace.cljs b/frontend/src/app/main/ui/workspace.cljs index a169ae616b..3d19f2934e 100644 --- a/frontend/src/app/main/ui/workspace.cljs +++ b/frontend/src/app/main/ui/workspace.cljs @@ -9,7 +9,6 @@ (: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] @@ -43,13 +42,6 @@ [okulary.core :as l] [rumext.v2 :as mf])) -(defn- make-workspace-ready-ref - [file-id] - (l/derived (fn [state] - (and (= file-id (:workspace-ready state)) - (some? (dsh/lookup-file-data state file-id)))) - st/state)) - (mf/defc workspace-content* {::mf/private true} [{:keys [file layout page wglobal]}] @@ -129,35 +121,32 @@ (mf/defc workspace-page* {::mf/private true} - [{:keys [page-id file layout wglobal]}] - (let [page-id (hooks/use-equal-memo page-id) - page (mf/deref refs/workspace-page)] + [{:keys [page-id file-id file layout wglobal]}] + (let [page (mf/deref refs/workspace-page)] (mf/with-effect [] (let [focus-out #(st/emit! (dw/workspace-focus-lost)) key (events/listen globals/window "blur" focus-out)] (partial events/unlistenByKey key))) - (mf/with-effect [page-id] - (if (some? page-id) - (st/emit! (dw/initialize-page page-id)) - (st/emit! (dcm/go-to-workspace ::rt/replace true))) - + (mf/with-effect [file-id page-id] + (st/emit! (dw/initialize-page file-id page-id)) (fn [] - (when (some? page-id) - (st/emit! (dw/finalize-page page-id))))) + (when page-id + (st/emit! (dw/finalize-page file-id page-id))))) (if (some? page) [:> workspace-content* {:file file :page page :wglobal wglobal :layout layout}] - [:& workspace-loader*]))) - + [:> workspace-loader*]))) (def ^:private ref:file-without-data (l/derived (fn [file] - (dissoc file :data)) + (-> file + (dissoc :data) + (assoc ::has-data (contains? file :data)))) refs/file =)) @@ -181,10 +170,6 @@ read-only? (mf/deref refs/workspace-read-only?) read-only? (or read-only? (not (:can-edit permissions))) - ready* (mf/with-memo [file-id] - (make-workspace-ready-ref file-id)) - ready? (mf/deref ready*) - design-tokens? (features/use-feature "design-tokens/v1") background-color (:background-color wglobal)] @@ -207,6 +192,10 @@ (st/emit! ::dps/force-persist (dw/finalize-workspace file-id)))) + (mf/with-effect [file page-id] + (when-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} [:> (mf/provider ctx/current-file-id) {:value file-id} [:> (mf/provider ctx/current-page-id) {:value page-id} @@ -219,9 +208,11 @@ :touch-action "none"}} [:> context-menu*] - (if ^boolean ready? - [:> workspace-page* {:page-id page-id - :file file - :wglobal wglobal - :layout layout}] + (if (::has-data file) + [:> workspace-page* + {:page-id page-id + :file-id file-id + :file file + :wglobal wglobal + :layout layout}] [:> workspace-loader*])]]]]]]])) diff --git a/frontend/test/frontend_tests/logic/copying_and_duplicating_test.cljs b/frontend/test/frontend_tests/logic/copying_and_duplicating_test.cljs index 3d331c616f..a6a59047a3 100644 --- a/frontend/test/frontend_tests/logic/copying_and_duplicating_test.cljs +++ b/frontend/test/frontend_tests/logic/copying_and_duplicating_test.cljs @@ -63,10 +63,10 @@ target-container-id (or target-container-id (:parent-id shape))] (filter some? - [(when target-page-id (dw/initialize-page target-page-id)) + [(when target-page-id (dw/initialize-page (:id file) target-page-id)) (dws/select-shape target-container-id) (dw/paste-shapes pdata) - (when target-page-id (dw/initialize-page (:id page)))]))) + (when target-page-id (dw/initialize-page (:id file) (:id page)))]))) (defn- sync-file [file] (map (fn [component-tag] From 272bbdd54a300e177e70525f599701bc42b54157 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marina=20L=C3=B3pez?= Date: Mon, 17 Feb 2025 11:31:23 +0100 Subject: [PATCH 4/6] :tada: Add AB test for empty workspace set board tool by default --- frontend/src/app/main/data/workspace.cljs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index f870bb2541..3fd0083e26 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -270,6 +270,16 @@ (rx/of (dws/select-shapes frames-id) dwz/zoom-to-selected-shape))))) +(defn- select-frame-tool + [page-id file-id] + (ptk/reify ::select-frame-tool + ptk/WatchEvent + (watch [_ state _] + (let [objects (dsh/lookup-page-objects state file-id page-id) + is-page-empty? (= (count objects) 1)] + (when is-page-empty? + (rx/of (dwd/select-for-drawing :frame))))))) + (defn- fetch-bundle "Multi-stage file bundle fetch coordinator" [file-id] @@ -463,7 +473,9 @@ (if-let [page (dsh/lookup-page state file-id page-id)] (rx/of (initialize-page* file-id page-id page) (dwth/watch-state-changes file-id page-id) - (dwl/watch-component-changes)) + (dwl/watch-component-changes) + (when (cf/external-feature-flag "boards-02" "test") + (select-frame-tool page-id file-id))) (rx/of (dcm/go-to-workspace :file-id file-id ::rt/replace true)))))) (defn finalize-page From 7d840722c4f4d8c11db3f56d5b9286334bca9f63 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 18 Feb 2025 16:55:16 +0100 Subject: [PATCH 5/6] :sparkles: Add abstraction for page emptiness checking --- common/src/app/common/types/page.cljc | 6 ++++++ frontend/src/app/main/data/workspace.cljs | 10 +++++----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/common/src/app/common/types/page.cljc b/common/src/app/common/types/page.cljc index 8c57f33094..cb0d0e9e0b 100644 --- a/common/src/app/common/types/page.cljc +++ b/common/src/app/common/types/page.cljc @@ -5,6 +5,7 @@ ;; Copyright (c) KALEIDOS INC (ns app.common.types.page + (:refer-clojure :exclude [empty?]) (:require [app.common.data :as d] [app.common.geom.point :as-alias gpt] @@ -98,3 +99,8 @@ (defn get-frame-flow [flows frame-id] (d/seek #(= (:starting-frame %) frame-id) (vals flows))) + +(defn is-empty? + "Check if page is empty or contains shapes" + [page] + (= 1 (count (:objects page)))) diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index 3fd0083e26..b4f02eabf5 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -29,6 +29,7 @@ [app.common.types.components-list :as ctkl] [app.common.types.container :as ctn] [app.common.types.file :as ctf] + [app.common.types.page :as ctp] [app.common.types.shape :as cts] [app.common.types.shape-tree :as ctst] [app.common.types.shape.layout :as ctl] @@ -271,13 +272,12 @@ dwz/zoom-to-selected-shape))))) (defn- select-frame-tool - [page-id file-id] + [file-id page-id] (ptk/reify ::select-frame-tool ptk/WatchEvent (watch [_ state _] - (let [objects (dsh/lookup-page-objects state file-id page-id) - is-page-empty? (= (count objects) 1)] - (when is-page-empty? + (let [page (dsh/lookup-page state file-id page-id)] + (when (ctp/is-empty? page) (rx/of (dwd/select-for-drawing :frame))))))) (defn- fetch-bundle @@ -475,7 +475,7 @@ (dwth/watch-state-changes file-id page-id) (dwl/watch-component-changes) (when (cf/external-feature-flag "boards-02" "test") - (select-frame-tool page-id file-id))) + (select-frame-tool file-id page-id))) (rx/of (dcm/go-to-workspace :file-id file-id ::rt/replace true)))))) (defn finalize-page From d019afe667fc3d30b57b7a03c67d7cd30ef9a927 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?luis=CE=B4=CE=BC?= Date: Tue, 18 Feb 2025 17:25:43 +0100 Subject: [PATCH 6/6] :bug: Fix incorrect number of replies in comments (#5893) --- frontend/src/app/main/ui/comments.cljs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/frontend/src/app/main/ui/comments.cljs b/frontend/src/app/main/ui/comments.cljs index 9843f1b335..e607e07e7a 100644 --- a/frontend/src/app/main/ui/comments.cljs +++ b/frontend/src/app/main/ui/comments.cljs @@ -621,9 +621,10 @@ [:> comment-content* {:content (:content item)}]] [:div {:class (stl/css :replies)} - (let [total-comments (:count-comments item 1) - total-replies (dec total-comments) - unread-replies (:count-unread-comments item 0)] + (let [total-comments (:count-comments item) + unread-comments (:count-unread-comments item) + total-replies (dec total-comments) + unread-replies (if (= unread-comments total-comments) (dec unread-comments) unread-comments)] [:* (when (> total-replies 0) (if (= total-replies 1)