diff --git a/CHANGES.md b/CHANGES.md index aa6c68295..d58a99268 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -52,6 +52,7 @@ ### :sparkles: New features +- Add some cosmetic changes in viewer mode [Taiga #3688](https://tree.taiga.io/project/penpot/us/3688) - Allow for nested and rotated boards inside other boards and groups [Taiga #2874](https://tree.taiga.io/project/penpot/us/2874?milestone=319982) - View mode improvements to enable access and use in different conditions [Taiga #3023](https://tree.taiga.io/project/penpot/us/3023) - Improved share link options. Now you can allow non-team members to comment and/or inspect [Taiga #3056] (https://tree.taiga.io/project/penpot/us/3056) @@ -63,7 +64,10 @@ ### :bug: Bugs fixed -- Fix font rendering on grid thumbnails [Taiga #3473](https://tree.taiga.io/project/penpot/issue/3473) +- Fix viewer scroll problems [Taiga 3403](https://tree.taiga.io/project/penpot/issue/3403) +- Fix hide html options on handoff [Taiga 3533](https://tree.taiga.io/project/penpot/issue/3533) +- Fix share prototypes overlay and stroke [Taiga #3994](https://tree.taiga.io/project/penpot/issue/3994) +- Fix border radious on boolean operations [Taiga #3959](https://tree.taiga.io/project/penpot/issue/3959) - Fix inconsistent representation of rectangles [Taiga #3977](https://tree.taiga.io/project/penpot/issue/3977) - Fix recent fonts info [Taiga #3953](https://tree.taiga.io/project/penpot/issue/3953) - Fix clipped elements affect boards and centering [Taiga #3666](https://tree.taiga.io/project/penpot/issue/3666) @@ -80,6 +84,8 @@ - Fix props preserving on copy&paste texts [Taiga #3629](https://tree.taiga.io/project/penpot/issue/3629) by @andrewzhurov - Fix unexpected layers ungrouping on moving it [Taiga #3932](https://tree.taiga.io/project/penpot/issue/3932) by @andrewzhurov - Fix unexpected exception and behavior on colorpicker with gradients [Taiga #3448](https://tree.taiga.io/project/penpot/issue/3448) +- Fix multiselection with shift not working inside a library group [Taiga #3532](https://tree.taiga.io/project/penpot/issue/3532) +- Fix drag and drop graphic assets in groups [Taiga #4002](https://tree.taiga.io/project/penpot/issue/4002) ### :arrow_up: Deps updates ### :heart: Community contributions by (Thank you!) @@ -91,6 +97,8 @@ - Fix colors from unlinked libs in color selected widget [Taiga #3712](https://tree.taiga.io/project/penpot/issue/3712) - Fix fill information not complete when paste plain text [Taiga #3680](https://tree.taiga.io/project/penpot/issue/3680) - Fix problem when resizing groups [Taiga #3702](https://tree.taiga.io/project/penpot/issue/3702) +- Fix issues on typographies assets grouping [#2073](https://github.com/penpot/penpot/issues/2073) +- Fix text positioning inconsistencies between browsers ## 1.14.1-beta diff --git a/backend/src/app/metrics.clj b/backend/src/app/metrics.clj index 2ae0223dc..4c5e10e19 100644 --- a/backend/src/app/metrics.clj +++ b/backend/src/app/metrics.clj @@ -52,6 +52,12 @@ :labels ["name"] :type :histogram} + :rpc-command-timing + {:name "rpc_command_timing" + :help "RPC command method call timming." + :labels ["name"] + :type :histogram} + :rpc-query-timing {:name "rpc_query_timing" :help "RPC query method call timing." diff --git a/backend/src/app/rpc.clj b/backend/src/app/rpc.clj index c1a175682..13c2cd3ff 100644 --- a/backend/src/app/rpc.clj +++ b/backend/src/app/rpc.clj @@ -204,7 +204,6 @@ (defn- process-method [cfg vfn] (let [mdata (meta vfn)] - ;; (prn mdata) [(keyword (::sv/name mdata)) (wrap cfg vfn mdata)])) diff --git a/backend/src/app/rpc/commands/binfile.clj b/backend/src/app/rpc/commands/binfile.clj index 6885f5cf5..764f0651e 100644 --- a/backend/src/app/rpc/commands/binfile.clj +++ b/backend/src/app/rpc/commands/binfile.clj @@ -728,11 +728,16 @@ [data] (letfn [(process-map-form [form] (cond-> form - ;; Relink Image Shapes + ;; Relink image shapes (and (map? (:metadata form)) (= :image (:type form))) (update-in [:metadata :id] lookup-index) + ;; Relink paths with fill image + (and (map? (:fill-image form)) + (= :path (:type form))) + (update-in [:fill-image :id] lookup-index) + ;; This covers old shapes and the new :fills. (uuid? (:fill-color-ref-file form)) (update :fill-color-ref-file lookup-index) diff --git a/backend/src/app/rpc/commands/comments.clj b/backend/src/app/rpc/commands/comments.clj index f7bf9e521..0e4ce7ff6 100644 --- a/backend/src/app/rpc/commands/comments.clj +++ b/backend/src/app/rpc/commands/comments.clj @@ -496,16 +496,15 @@ ;; --- COMMAND: Update comment thread position (s/def ::update-comment-thread-position - (s/keys :req-un [::profile-id ::id ::position ::frame-id])) + (s/keys :req-un [::profile-id ::id ::position ::frame-id] + :opt-un [::share-id])) (sv/defmethod ::update-comment-thread-position {::doc/added "1.15"} - [{:keys [pool] :as cfg} {:keys [profile-id id position frame-id] :as params}] + [{:keys [pool] :as cfg} {:keys [profile-id id position frame-id share-id] :as params}] (db/with-atomic [conn pool] (let [thread (db/get-by-id conn :comment-thread id {:for-update true})] - (when-not (= (:owner-id thread) profile-id) - (ex/raise :type :validation - :code :not-allowed)) + (files/check-comment-permissions! conn profile-id (:file-id thread) share-id) (db/update! conn :comment-thread {:modified-at (dt/now) :position (db/pgpoint position) @@ -516,16 +515,15 @@ ;; --- COMMAND: Update comment frame (s/def ::update-comment-thread-frame - (s/keys :req-un [::profile-id ::id ::frame-id])) + (s/keys :req-un [::profile-id ::id ::frame-id] + :opt-un [::share-id])) (sv/defmethod ::update-comment-thread-frame {::doc/added "1.15"} - [{:keys [pool] :as cfg} {:keys [profile-id id frame-id] :as params}] + [{:keys [pool] :as cfg} {:keys [profile-id id frame-id share-id] :as params}] (db/with-atomic [conn pool] (let [thread (db/get-by-id conn :comment-thread id {:for-update true})] - (when-not (= (:owner-id thread) profile-id) - (ex/raise :type :validation - :code :not-allowed)) + (files/check-comment-permissions! conn profile-id (:file-id thread) share-id) (db/update! conn :comment-thread {:modified-at (dt/now) :frame-id frame-id} diff --git a/backend/test/app/services_media_test.clj b/backend/test/app/services_media_test.clj index aa5f9f5ca..a5f971d90 100644 --- a/backend/test/app/services_media_test.clj +++ b/backend/test/app/services_media_test.clj @@ -46,14 +46,11 @@ (t/is (sto/storage-object? mobj1)) (t/is (sto/storage-object? mobj2)) (t/is (= 122785 (:size mobj1))) - ;; This is because in ubuntu 21.04 generates different ;; thumbnail that in ubuntu 22.04. This hack should be removed ;; when we all use the ubuntu 22.04 devenv image. - (t/is (or - (= 3302 (:size mobj2)) - (= 3303 (:size mobj2)))))) - )) + (t/is (or (= 3302 (:size mobj2)) + (= 3303 (:size mobj2)))))))) (t/deftest media-object-upload (let [prof (th/create-profile* 1) diff --git a/common/src/app/common/pages/focus.cljc b/common/src/app/common/pages/focus.cljc index 57a048b2e..7feab505a 100644 --- a/common/src/app/common/pages/focus.cljc +++ b/common/src/app/common/pages/focus.cljc @@ -41,6 +41,5 @@ (defn is-in-focus? [objects focus id] - (d/seek - #(contains? focus %) - (cph/get-parents-seq objects id))) + (d/seek (partial contains? focus) + (cons id (cph/get-parent-ids objects id)))) diff --git a/common/src/app/common/pages/helpers.cljc b/common/src/app/common/pages/helpers.cljc index c3bf84e70..8f4102c01 100644 --- a/common/src/app/common/pages/helpers.cljc +++ b/common/src/app/common/pages/helpers.cljc @@ -79,16 +79,6 @@ [objects id] (-> objects (get id) :parent-id)) -(defn get-parents-seq - [objects shape-id] - - (cond - (nil? shape-id) - nil - - :else - (lazy-seq (cons shape-id (get-parents-seq objects (get-in objects [shape-id :parent-id])))))) - (defn get-parent-ids "Returns a vector of parents of the specified shape." [objects shape-id] @@ -430,7 +420,7 @@ (defn is-child? [objects parent-id candidate-child-id] - (let [parents (get-parents-seq objects candidate-child-id)] + (let [parents (get-parent-ids objects candidate-child-id)] (some? (d/seek #(= % parent-id) parents)))) (defn reduce-objects @@ -470,10 +460,9 @@ (defn get-shape-id-root-frame [objects shape-id] - (->> (get-parents-seq objects shape-id) + (->> (get-parent-ids objects shape-id) + (cons shape-id) (map (d/getf objects)) - (d/seek #(and (= :frame (:type %)) - (= uuid/zero (:frame-id %)))) - + (d/seek root-frame?) :id)) diff --git a/common/src/app/common/types/shape_tree.cljc b/common/src/app/common/types/shape_tree.cljc index 67fac58df..eba4c065e 100644 --- a/common/src/app/common/types/shape_tree.cljc +++ b/common/src/app/common/types/shape_tree.cljc @@ -132,8 +132,8 @@ (defn get-base [objects id-a id-b] - (let [parents-a (reverse (cph/get-parents-seq objects id-a)) - parents-b (reverse (cph/get-parents-seq objects id-b)) + (let [parents-a (reverse (cons id-a (cph/get-parent-ids objects id-a))) + parents-b (reverse (cons id-b (cph/get-parent-ids objects id-b))) [base base-child-a base-child-b] (loop [parents-a (rest parents-a) diff --git a/docker/devenv/Dockerfile b/docker/devenv/Dockerfile index d3af5c86a..3a7138921 100644 --- a/docker/devenv/Dockerfile +++ b/docker/devenv/Dockerfile @@ -3,7 +3,7 @@ LABEL maintainer="Andrey Antukh " ARG DEBIAN_FRONTEND=noninteractive -ENV NODE_VERSION=v16.16.0 \ +ENV NODE_VERSION=v16.17.0 \ CLOJURE_VERSION=1.11.1.1149 \ CLJKONDO_VERSION=2022.06.22 \ BABASHKA_VERSION=0.8.156 \ diff --git a/docker/images/Dockerfile.exporter b/docker/images/Dockerfile.exporter index 5b1e90458..144b5a9c1 100644 --- a/docker/images/Dockerfile.exporter +++ b/docker/images/Dockerfile.exporter @@ -5,7 +5,7 @@ ARG DEBIAN_FRONTEND=noninteractive ENV LANG=en_US.UTF-8 \ LC_ALL=en_US.UTF-8 \ - NODE_VERSION=v16.15.1 + NODE_VERSION=v16.17.0 RUN set -ex; \ mkdir -p /etc/resolvconf/resolv.conf.d; \ diff --git a/docker/images/files/config.js b/docker/images/files/config.js index ef86caa0e..7bc9ce940 100644 --- a/docker/images/files/config.js +++ b/docker/images/files/config.js @@ -1,13 +1,2 @@ // Frontend configuration - -//var penpotPublicURI = "https://penpot.example.com"; -//var penpotDemoWarning = ; -//var penpotAllowDemoUsers = ; -//var penpotGoogleClientID = ""; -//var penpotGitlabClientID = ""; -//var penpotGithubClientID = ""; -//var penpotOIDCClientID = ""; -//var penpotLoginWithLDAP = ; -//var penpotRegistrationEnabled = ; -//var penpotAnalyticsEnabled = ; //var penpotFlags = ""; diff --git a/frontend/resources/styles/main/partials/handoff.scss b/frontend/resources/styles/main/partials/handoff.scss index adcb45082..a4eb7dd4f 100644 --- a/frontend/resources/styles/main/partials/handoff.scss +++ b/frontend/resources/styles/main/partials/handoff.scss @@ -330,26 +330,13 @@ } .code-row-lang { + color: $color-gray-10; position: relative; display: flex; flex-direction: row; + font-size: $fs14; margin: 0.5rem; - .code-selection { - height: 100%; - margin: 0; - padding: 0.5rem; - width: 4.5rem; - font-size: $fs12; - background: $color-gray-50; - color: $color-gray-10; - border-radius: 2px; - border: 1px solid $color-gray-30; - background-image: url("/images/icons/arrow-down-white.svg"); - background-repeat: no-repeat; - background-position: 90% 48%; - background-size: 8px; - } .expand-button, .copy-button { margin-top: 8px; diff --git a/frontend/resources/styles/main/partials/share-link.scss b/frontend/resources/styles/main/partials/share-link.scss index 2422a2470..920f67344 100644 --- a/frontend/resources/styles/main/partials/share-link.scss +++ b/frontend/resources/styles/main/partials/share-link.scss @@ -1,4 +1,5 @@ .share-modal { + background: none; display: block; top: 50px; left: calc(100vw - 500px); @@ -7,6 +8,7 @@ width: 480px; background-color: $color-white; box-shadow: 0px 2px 8px 0px rgb(0 0 0 / 20%); + .modal-content { padding: 16px 32px; &:first-child { diff --git a/frontend/resources/styles/main/partials/viewer.scss b/frontend/resources/styles/main/partials/viewer.scss index 637429a4a..bb1bb6af6 100644 --- a/frontend/resources/styles/main/partials/viewer.scss +++ b/frontend/resources/styles/main/partials/viewer.scss @@ -30,7 +30,6 @@ grid-row: 1 / span 2; grid-column: 1 / span 1; display: flex; - justify-content: center; align-items: center; flex-flow: wrap; overflow: auto; @@ -72,8 +71,8 @@ } & .viewer-go-next { - right: 0; - padding-right: 29px; + right: 8px; + width: 46px; svg { margin-left: 2px; } @@ -97,12 +96,13 @@ & .viewer-bottom { position: absolute; - bottom: 0; - height: 50px; + bottom: 8px; + height: 30px; width: 100%; display: flex; justify-content: space-between; align-items: center; + z-index: 2; &.left-bar { width: calc(100% - 512px); @@ -144,6 +144,8 @@ } & .viewer-wrapper { + margin-left: auto; + margin-right: auto; position: relative; } @@ -180,6 +182,7 @@ top: 50px; width: 256px; height: 100%; + z-index: 10; } } diff --git a/frontend/src/app/main/data/comments.cljs b/frontend/src/app/main/data/comments.cljs index 016f074ea..67d96a2f1 100644 --- a/frontend/src/app/main/data/comments.cljs +++ b/frontend/src/app/main/data/comments.cljs @@ -342,6 +342,13 @@ (some? list) (assoc :list list))))))) +(defn update-options + [params] + (ptk/reify ::update-options + ptk/UpdateEvent + (update [_ state] + (update state :comments-local merge params)))) + (s/def ::create-draft-params (s/keys :req-un [::page-id ::file-id ::position])) diff --git a/frontend/src/app/main/data/viewer.cljs b/frontend/src/app/main/data/viewer.cljs index cde149b2a..1f10b7007 100644 --- a/frontend/src/app/main/data/viewer.cljs +++ b/frontend/src/app/main/data/viewer.cljs @@ -7,6 +7,7 @@ (ns app.main.data.viewer (:require [app.common.data :as d] + [app.common.data.macros :as dm] [app.common.geom.point :as gpt] [app.common.pages.helpers :as cph] [app.common.spec :as us] @@ -22,6 +23,9 @@ [cljs.spec.alpha :as s] [potok.core :as ptk])) +(s/def ::nilable-boolean (s/nilable ::us/boolean)) +(s/def ::nilable-animation (s/nilable ::ctsi/animation)) + ;; --- Local State Initialization (def ^:private @@ -34,7 +38,6 @@ :comments-show :unresolved :selected #{} :collapsed #{} - :overlays [] :hover nil :share-id "" :file-comments-users []}) @@ -351,7 +354,8 @@ (declare flash-done) -(def flash-interactions +(defn flash-interactions + [] (ptk/reify ::flash-interactions ptk/UpdateEvent (update [_ state] @@ -389,7 +393,7 @@ (ptk/reify ::complete-animation ptk/UpdateEvent (update [_ state] - (d/dissoc-in state [:viewer-local :current-animation])))) + (dissoc state :viewer-animation)))) ;; --- Navigation inside page @@ -398,7 +402,7 @@ (ptk/reify ::go-to-frame-by-index ptk/UpdateEvent (update [_ state] - (assoc-in state [:viewer-local :overlays] [])) + (assoc state :viewer-overlays [])) ptk/WatchEvent (watch [_ state _] @@ -413,8 +417,9 @@ (go-to-frame frame-id nil)) ([frame-id animation] - (us/verify ::us/uuid frame-id) - (us/verify (s/nilable ::ctsi/animation) animation) + (us/assert! ::us/uuid frame-id) + (us/assert! ::nilable-animation animation) + (ptk/reify ::go-to-frame ptk/UpdateEvent (update [_ state] @@ -426,13 +431,13 @@ frame (get frames index)] (cond-> state :always - (assoc-in [:viewer-local :overlays] []) + (assoc :viewer-overlays []) (some? animation) - (assoc-in [:viewer-local :current-animation] - {:kind :go-to-frame - :orig-frame-id (:id frame) - :animation animation})))) + (assoc :viewer-animation + {:kind :go-to-frame + :orig-frame-id (:id frame) + :animation animation})))) ptk/WatchEvent (watch [_ state _] @@ -462,7 +467,7 @@ (ptk/reify ::go-to-section ptk/UpdateEvent (update [_ state] - (assoc-in state [:viewer-local :overlays] [])) + (assoc state :viewer-overlays [])) ptk/WatchEvent (watch [_ state _] @@ -473,64 +478,69 @@ ;; --- Overlays -(defn- do-open-overlay +(defn- open-overlay* [state frame position close-click-outside background-overlay animation] (cond-> state :always - (update-in [:viewer-local :overlays] conj - {:frame frame - :position position - :close-click-outside close-click-outside - :background-overlay background-overlay}) - (some? animation) - (assoc-in [:viewer-local :current-animation] - {:kind :open-overlay - :overlay-id (:id frame) - :animation animation}))) + (update :viewer-overlays conj + {:frame frame + :id (:id frame) + :position position + :close-click-outside close-click-outside + :background-overlay background-overlay}) -(defn- do-close-overlay + (some? animation) + (assoc :viewer-animation + {:kind :open-overlay + :overlay-id (:id frame) + :animation animation}))) + +(defn- close-overlay* [state frame-id animation] (if (nil? animation) - (update-in state [:viewer-local :overlays] - (fn [overlays] - (d/removev #(= (:id (:frame %)) frame-id) overlays))) - (assoc-in state [:viewer-local :current-animation] - {:kind :close-overlay - :overlay-id frame-id - :animation animation}))) + (update state :viewer-overlays + (fn [overlays] + (d/removev #(= (:id (:frame %)) frame-id) overlays))) + (assoc state :viewer-animation + {:kind :close-overlay + :overlay-id frame-id + :animation animation}))) (defn open-overlay [frame-id position close-click-outside background-overlay animation] - (us/verify ::us/uuid frame-id) - (us/verify ::gpt/point position) - (us/verify (s/nilable ::us/boolean) close-click-outside) - (us/verify (s/nilable ::us/boolean) background-overlay) - (us/verify (s/nilable ::ctsi/animation) animation) + (us/assert! ::us/uuid frame-id) + (us/assert! ::gpt/point position) + (us/assert! ::nilable-boolean close-click-outside) + (us/assert! ::nilable-boolean background-overlay) + (us/assert! ::nilable-animation animation) + (ptk/reify ::open-overlay ptk/UpdateEvent (update [_ state] (let [route (:route state) qparams (:query-params route) page-id (:page-id qparams) - frames (get-in state [:viewer :pages page-id :all-frames]) + frames (dm/get-in state [:viewer :pages page-id :all-frames]) frame (d/seek #(= (:id %) frame-id) frames) - overlays (get-in state [:viewer-local :overlays])] + overlays (:viewer-overlays state)] (if-not (some #(= (:frame %) frame) overlays) - (do-open-overlay state - frame - position - close-click-outside - background-overlay - animation) + (open-overlay* state + frame + position + close-click-outside + background-overlay + animation) state))))) + (defn toggle-overlay [frame-id position close-click-outside background-overlay animation] - (us/verify ::us/uuid frame-id) - (us/verify ::gpt/point position) - (us/verify (s/nilable ::us/boolean) close-click-outside) - (us/verify (s/nilable ::us/boolean) background-overlay) - (us/verify (s/nilable ::ctsi/animation) animation) + (us/assert! ::us/uuid frame-id) + (us/assert! ::gpt/point position) + (us/assert! ::nilable-boolean close-click-outside) + (us/assert! ::nilable-boolean background-overlay) + (us/assert! ::nilable-animation animation) + (ptk/reify ::toggle-overlay ptk/UpdateEvent (update [_ state] @@ -539,29 +549,30 @@ page-id (:page-id qparams) frames (get-in state [:viewer :pages page-id :all-frames]) frame (d/seek #(= (:id %) frame-id) frames) - overlays (get-in state [:viewer-local :overlays])] + overlays (:viewer-overlays state)] (if-not (some #(= (:frame %) frame) overlays) - (do-open-overlay state - frame - position - close-click-outside - background-overlay - animation) - (do-close-overlay state - (:id frame) - (ctsi/invert-direction animation))))))) + (open-overlay* state + frame + position + close-click-outside + background-overlay + animation) + (close-overlay* state + (:id frame) + (ctsi/invert-direction animation))))))) (defn close-overlay ([frame-id] (close-overlay frame-id nil)) ([frame-id animation] - (us/verify ::us/uuid frame-id) - (us/verify (s/nilable ::ctsi/animation) animation) + (us/assert! ::us/uuid frame-id) + (us/assert! ::nilable-animation animation) + (ptk/reify ::close-overlay ptk/UpdateEvent (update [_ state] - (do-close-overlay state - frame-id - animation))))) + (close-overlay* state + frame-id + animation))))) ;; --- Objects selection diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index 69203edeb..ae2795c36 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -22,6 +22,7 @@ [app.common.types.shape-tree :as ctst] [app.common.uuid :as uuid] [app.config :as cfg] + [app.main.data.comments :as dcm] [app.main.data.events :as ev] [app.main.data.messages :as msg] [app.main.data.users :as du] @@ -115,7 +116,8 @@ ptk/WatchEvent (watch [_ _ stream] (rx/merge - (rx/of (dwp/fetch-bundle project-id file-id)) + (rx/of (dwp/fetch-bundle project-id file-id) + (dcm/retrieve-comment-threads file-id)) ;; Initialize notifications (websocket connection) and the file persistence (->> stream diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index a3f0b6765..3987fef61 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -238,10 +238,12 @@ (defn- do-update-tipography [it state typography file-id] - (let [data (get state :workspace-data) - changes (-> (pcb/empty-changes it) - (pcb/with-library-data data) - (pcb/update-typography typography))] + (let [data (get state :workspace-data) + [path name] (cph/parse-path-name (:name typography)) + typography (assoc typography :path path :name name) + changes (-> (pcb/empty-changes it) + (pcb/with-library-data data) + (pcb/update-typography typography))] (rx/of (dwu/start-undo-transaction) (dch/commit-changes changes) (sync-file (:current-file-id state) file-id :typographies (:id typography)) @@ -264,9 +266,9 @@ (ptk/reify ::rename-typography ptk/WatchEvent (watch [it state _] - (let [data (get state :workspace-data) + (let [data (get state :workspace-data) [path name] (cph/parse-path-name new-name) - object (get-in data [:typographies id]) + object (get-in data [:typographies id]) new-object (assoc object :path path :name name)] (do-update-tipography it state new-object file-id))))) diff --git a/frontend/src/app/main/data/workspace/transforms.cljs b/frontend/src/app/main/data/workspace/transforms.cljs index 4357efc9a..ee6f746ef 100644 --- a/frontend/src/app/main/data/workspace/transforms.cljs +++ b/frontend/src/app/main/data/workspace/transforms.cljs @@ -17,6 +17,7 @@ [app.common.pages.helpers :as cph] [app.common.spec :as us] [app.common.types.shape-tree :as ctt] + [app.common.uuid :as uuid] [app.main.data.workspace.changes :as dch] [app.main.data.workspace.collapse :as dwc] [app.main.data.workspace.comments :as-alias dwcm] @@ -762,7 +763,17 @@ changes (-> (pcb/empty-changes it page-id) (pcb/with-objects objects) - (pcb/update-shapes moving-frames (fn [shape] (assoc shape :hide-in-viewer true))) + (pcb/update-shapes moving-frames + (fn [shape] + ;; Hide in viwer must be enabled just when a board is moved + ;; inside another artboard an nested to it, we have to avoid + ;; situations like: - Moving inside the same frame - Moving + ;; outside the frame + (cond-> shape + (and (not= frame-id (:id shape)) + (not= frame-id (:frame-id shape)) + (not= frame-id uuid/zero)) + (assoc :hide-in-viewer true)))) (pcb/change-parent frame-id moving-shapes))] (when-not (empty? changes) diff --git a/frontend/src/app/main/ui.cljs b/frontend/src/app/main/ui.cljs index fd65d7569..768a86f1f 100644 --- a/frontend/src/app/main/ui.cljs +++ b/frontend/src/app/main/ui.cljs @@ -29,6 +29,7 @@ (mf/defc on-main-error [{:keys [error] :as props}] (mf/with-effect + (js/console.log error) (st/emit! (rt/assign-exception error))) [:span "Internal application error"]) diff --git a/frontend/src/app/main/ui/comments.cljs b/frontend/src/app/main/ui/comments.cljs index 14361b96f..31fe47767 100644 --- a/frontend/src/app/main/ui/comments.cljs +++ b/frontend/src/app/main/ui/comments.cljs @@ -6,6 +6,7 @@ (ns app.main.ui.comments (:require + [app.common.geom.point :as gpt] [app.config :as cfg] [app.main.data.comments :as dcm] [app.main.data.modal :as modal] @@ -110,8 +111,10 @@ [:input.btn-secondary {:type "button" :value "Cancel" :on-click on-cancel}]])])) (mf/defc draft-thread - [{:keys [draft zoom on-cancel on-submit] :as props}] - (let [position (:position draft) + [{:keys [draft zoom on-cancel on-submit position-modifier]}] + (let [position (cond-> (:position draft) + (some? position-modifier) + (gpt/transform position-modifier)) content (:content draft) pos-x (* (:x position) zoom) pos-y (* (:y position) zoom) @@ -281,9 +284,12 @@ (l/derived (l/in [:comments id]) st/state)) (mf/defc thread-comments - [{:keys [thread zoom users origin]}] + {::mf/wrap [mf/memo]} + [{:keys [thread zoom users origin position-modifier]}] (let [ref (mf/use-ref) - pos (:position thread) + pos (cond-> (:position thread) + (some? position-modifier) + (gpt/transform position-modifier)) pos-x (+ (* (:x pos) zoom) 14) pos-y (- (* (:y pos) zoom) 14) @@ -384,9 +390,12 @@ (mf/defc thread-bubble {::mf/wrap [mf/memo]} - [{:keys [thread zoom open? on-click origin]}] - (let [pos (:position thread) - drag? (mf/use-ref nil) + [{:keys [thread zoom open? on-click origin position-modifier]}] + (let [pos (cond-> (:position thread) + (some? position-modifier) + (gpt/transform position-modifier)) + + drag? (mf/use-ref nil) was-open? (mf/use-ref nil) {:keys [on-pointer-down @@ -398,41 +407,45 @@ pos-x (* (or (:new-position-x @state) (:x pos)) zoom) pos-y (* (or (:new-position-y @state) (:y pos)) zoom) - on-pointer-down* (mf/use-callback - (mf/deps origin was-open? open? drag? on-pointer-down) - (fn [event] - (when (not= origin :viewer) - (mf/set-ref-val! was-open? open?) - (when open? (st/emit! (dcm/close-thread))) - (mf/set-ref-val! drag? false) - (dom/stop-propagation event) - (on-pointer-down event)))) + on-pointer-down* + (mf/use-callback + (mf/deps origin was-open? open? drag? on-pointer-down) + (fn [event] + (when (not= origin :viewer) + (mf/set-ref-val! was-open? open?) + (when open? (st/emit! (dcm/close-thread))) + (mf/set-ref-val! drag? false) + (dom/stop-propagation event) + (on-pointer-down event)))) - on-pointer-up* (mf/use-callback - (mf/deps origin thread was-open? drag? on-pointer-up) - (fn [event] - (when (not= origin :viewer) - (dom/stop-propagation event) - (on-pointer-up event thread) + on-pointer-up* + (mf/use-callback + (mf/deps origin thread was-open? drag? on-pointer-up) + (fn [event] + (when (not= origin :viewer) + (dom/stop-propagation event) + (on-pointer-up event thread) - (when (or (and (mf/ref-val was-open?) (mf/ref-val drag?)) - (and (not (mf/ref-val was-open?)) (not (mf/ref-val drag?)))) - (st/emit! (dcm/open-thread thread)))))) + (when (or (and (mf/ref-val was-open?) (mf/ref-val drag?)) + (and (not (mf/ref-val was-open?)) (not (mf/ref-val drag?)))) + (st/emit! (dcm/open-thread thread)))))) - on-mouse-move* (mf/use-callback - (mf/deps origin drag? on-mouse-move) - (fn [event] - (when (not= origin :viewer) - (mf/set-ref-val! drag? true) - (dom/stop-propagation event) - (on-mouse-move event)))) + on-mouse-move* + (mf/use-callback + (mf/deps origin drag? on-mouse-move) + (fn [event] + (when (not= origin :viewer) + (mf/set-ref-val! drag? true) + (dom/stop-propagation event) + (on-mouse-move event)))) - on-click* (mf/use-callback - (mf/deps origin thread on-click) - (fn [event] - (dom/stop-propagation event) - (when (= origin :viewer) - (on-click thread))))] + on-click* + (mf/use-callback + (mf/deps origin thread on-click) + (fn [event] + (dom/stop-propagation event) + (when (= origin :viewer) + (on-click thread))))] [:div.thread-bubble {:style {:top (str pos-y "px") @@ -448,7 +461,7 @@ [:span (:seqn thread)]])) (mf/defc comment-thread - [{:keys [item users on-click] :as props}] + [{:keys [item users on-click]}] (let [owner (get users (:owner-id item)) on-click* (mf/use-callback diff --git a/frontend/src/app/main/ui/context.cljs b/frontend/src/app/main/ui/context.cljs index 8ec9e69fa..8836001d5 100644 --- a/frontend/src/app/main/ui/context.cljs +++ b/frontend/src/app/main/ui/context.cljs @@ -8,12 +8,7 @@ (:require [rumext.alpha :as mf])) -(def render-ctx (mf/create-context nil)) -(def def-ctx (mf/create-context false)) - -;; This content is used to replace complex colors to simple ones -;; for text shapes in the export process -(def text-plain-colors-ctx (mf/create-context false)) +(def render-id (mf/create-context nil)) (def current-route (mf/create-context nil)) (def current-profile (mf/create-context nil)) @@ -21,8 +16,12 @@ (def current-project-id (mf/create-context nil)) (def current-page-id (mf/create-context nil)) (def current-file-id (mf/create-context nil)) -(def libraries (mf/create-context nil)) -(def scroll-ctx (mf/create-context nil)) -(def active-frames-ctx (mf/create-context nil)) + +(def active-frames (mf/create-context nil)) (def render-thumbnails (mf/create-context nil)) + +(def libraries (mf/create-context nil)) (def components-v2 (mf/create-context nil)) + +(def current-scroll (mf/create-context nil)) +(def current-zoom (mf/create-context nil)) diff --git a/frontend/src/app/main/ui/shapes/attrs.cljs b/frontend/src/app/main/ui/shapes/attrs.cljs index 4b95e4350..f94871a5f 100644 --- a/frontend/src/app/main/ui/shapes/attrs.cljs +++ b/frontend/src/app/main/ui/shapes/attrs.cljs @@ -159,7 +159,7 @@ (defn add-style-attrs ([props shape] - (let [render-id (mf/use-ctx muc/render-ctx)] + (let [render-id (mf/use-ctx muc/render-id)] (add-style-attrs props shape render-id))) ([props shape render-id] diff --git a/frontend/src/app/main/ui/shapes/custom_stroke.cljs b/frontend/src/app/main/ui/shapes/custom_stroke.cljs index c0eb8ddf4..7753c3f32 100644 --- a/frontend/src/app/main/ui/shapes/custom_stroke.cljs +++ b/frontend/src/app/main/ui/shapes/custom_stroke.cljs @@ -211,7 +211,7 @@ {::mf/wrap-props false} [props] - (let [render-id (mf/use-ctx muc/render-ctx) + (let [render-id (mf/use-ctx muc/render-id) child (obj/get props "children") base-props (obj/get child "props") elem-name (obj/get child "type") @@ -253,7 +253,7 @@ (mf/defc inner-stroke {::mf/wrap-props false} [props] - (let [render-id (mf/use-ctx muc/render-ctx) + (let [render-id (mf/use-ctx muc/render-id) child (obj/get props "children") base-props (obj/get child "props") elem-name (obj/get child "type") @@ -292,7 +292,7 @@ (let [child (obj/get props "children") shape (obj/get props "shape") - render-id (mf/use-ctx muc/render-ctx) + render-id (mf/use-ctx muc/render-id) index (obj/get props "index") stroke-width (:stroke-width shape 0) stroke-style (:stroke-style shape :none) @@ -417,7 +417,7 @@ shape (obj/get props "shape") elem-name (obj/get child "type") position (or (obj/get props "position") 0) - render-id (or (obj/get props "render-id") (mf/use-ctx muc/render-ctx))] + render-id (or (obj/get props "render-id") (mf/use-ctx muc/render-id))] [:g {:id (dm/fmt "fills-%" (:id shape))} [:> elem-name (build-fill-props shape child position render-id)]])) @@ -427,7 +427,7 @@ (let [child (obj/get props "children") shape (obj/get props "shape") elem-name (obj/get child "type") - render-id (or (obj/get props "render-id") (mf/use-ctx muc/render-ctx)) + render-id (or (obj/get props "render-id") (mf/use-ctx muc/render-id)) stroke-id (dm/fmt "strokes-%" (:id shape)) stroke-props (-> (obj/create) (obj/set! "id" stroke-id) diff --git a/frontend/src/app/main/ui/shapes/export.cljs b/frontend/src/app/main/ui/shapes/export.cljs index 5f8ceba50..b2453f7e0 100644 --- a/frontend/src/app/main/ui/shapes/export.cljs +++ b/frontend/src/app/main/ui/shapes/export.cljs @@ -286,7 +286,7 @@ (for [[index fill] (d/enumerate fills)] [:> "penpot:fill" #js {:penpot:fill-color (if (some? (:fill-color-gradient fill)) - (str/format "url(#%s)" (str "fill-color-gradient_" (mf/use-ctx muc/render-ctx) "_" index)) + (str/format "url(#%s)" (str "fill-color-gradient_" (mf/use-ctx muc/render-id) "_" index)) (d/name (:fill-color fill))) :penpot:fill-color-ref-file (d/name (:fill-color-ref-file fill)) :penpot:fill-color-ref-id (d/name (:fill-color-ref-id fill)) @@ -299,7 +299,7 @@ (for [[index stroke] (d/enumerate strokes)] [:> "penpot:stroke" #js {:penpot:stroke-color (if (some? (:stroke-color-gradient stroke)) - (str/format "url(#%s)" (str "stroke-color-gradient_" (mf/use-ctx muc/render-ctx) "_" index)) + (str/format "url(#%s)" (str "stroke-color-gradient_" (mf/use-ctx muc/render-id) "_" index)) (d/name (:stroke-color stroke))) :penpot:stroke-color-ref-file (d/name (:stroke-color-ref-file stroke)) :penpot:stroke-color-ref-id (d/name (:stroke-color-ref-id stroke)) diff --git a/frontend/src/app/main/ui/shapes/frame.cljs b/frontend/src/app/main/ui/shapes/frame.cljs index 1aa7a7b5b..ba821a5d7 100644 --- a/frontend/src/app/main/ui/shapes/frame.cljs +++ b/frontend/src/app/main/ui/shapes/frame.cljs @@ -62,7 +62,7 @@ :height height :className "frame-background"})) path? (some? (.-d props)) - render-id (mf/use-ctx muc/render-ctx)] + render-id (mf/use-ctx muc/render-id)] [:* [:g {:clip-path (when (not show-content) (frame-clip-url shape render-id))} diff --git a/frontend/src/app/main/ui/shapes/gradients.cljs b/frontend/src/app/main/ui/shapes/gradients.cljs index a57678bf4..9d45482f4 100644 --- a/frontend/src/app/main/ui/shapes/gradients.cljs +++ b/frontend/src/app/main/ui/shapes/gradients.cljs @@ -104,7 +104,7 @@ (let [attr (obj/get props "attr") shape (obj/get props "shape") id (obj/get props "id") - id' (mf/use-ctx muc/render-ctx) + id' (mf/use-ctx muc/render-id) id (or id (dm/str (name attr) "_" id')) gradient (get shape attr) gradient-props #js {:id id diff --git a/frontend/src/app/main/ui/shapes/group.cljs b/frontend/src/app/main/ui/shapes/group.cljs index 88dc6186d..664c4e834 100644 --- a/frontend/src/app/main/ui/shapes/group.cljs +++ b/frontend/src/app/main/ui/shapes/group.cljs @@ -21,7 +21,7 @@ (let [shape (unchecked-get props "shape") childs (unchecked-get props "childs") objects (unchecked-get props "objects") - render-id (mf/use-ctx muc/render-ctx) + render-id (mf/use-ctx muc/render-id) masked-group? (:masked-group? shape) [mask childs] (if masked-group? diff --git a/frontend/src/app/main/ui/shapes/mask.cljs b/frontend/src/app/main/ui/shapes/mask.cljs index 5c353d120..c43b12a60 100644 --- a/frontend/src/app/main/ui/shapes/mask.cljs +++ b/frontend/src/app/main/ui/shapes/mask.cljs @@ -47,18 +47,18 @@ {::mf/wrap-props false} [props] (let [mask (unchecked-get props "mask") - render-id (mf/use-ctx muc/render-ctx) + render-id (mf/use-ctx muc/render-id) svg-text? (and (= :text (:type mask)) (some? (:position-data mask))) ;; This factory is generic, it's used for viewer, workspace and handoff. - ;; These props are generated in viewer wrappers only, in the rest of the - ;; cases these props will be nil, not affecting the code. + ;; These props are generated in viewer wrappers only, in the rest of the + ;; cases these props will be nil, not affecting the code. fixed? (unchecked-get props "fixed?") delta (unchecked-get props "delta") mask-bb (-> (gsh/transform-shape mask) (cond-> fixed? (gsh/move delta)) (:points)) - + mask-bb-rect (gsh/points->rect mask-bb)] [:defs [:filter {:id (filter-id render-id mask)} diff --git a/frontend/src/app/main/ui/shapes/shape.cljs b/frontend/src/app/main/ui/shapes/shape.cljs index c7ea0ebf3..a457534a7 100644 --- a/frontend/src/app/main/ui/shapes/shape.cljs +++ b/frontend/src/app/main/ui/shapes/shape.cljs @@ -9,8 +9,8 @@ [app.common.data :as d] [app.common.data.macros :as dm] [app.common.pages.helpers :as cph] - [app.common.uuid :as uuid] [app.main.ui.context :as muc] + [app.main.ui.hooks :as h] [app.main.ui.shapes.attrs :as attrs] [app.main.ui.shapes.export :as ed] [app.main.ui.shapes.fills :as fills] @@ -49,18 +49,19 @@ {::mf/forward-ref true ::mf/wrap-props false} [props ref] - (let [shape (obj/get props "shape") - children (obj/get props "children") - pointer-events (obj/get props "pointer-events") - disable-shadows? (obj/get props "disable-shadows?") - type (:type shape) - render-id (mf/use-memo #(str (uuid/next))) - filter-id (str "filter_" render-id) - styles (-> (obj/create) - (obj/set! "pointerEvents" pointer-events) - (cond-> (and (:blend-mode shape) (not= (:blend-mode shape) :normal)) - (obj/set! "mixBlendMode" (d/name (:blend-mode shape))))) + (let [shape (unchecked-get props "shape") + children (unchecked-get props "children") + pointer-events (unchecked-get props "pointer-events") + disable-shadows? (unchecked-get props "disable-shadows?") + + type (:type shape) + render-id (h/use-id) + filter-id (dm/str "filter_" render-id) + styles (-> (obj/create) + (obj/set! "pointerEvents" pointer-events) + (cond-> (and (:blend-mode shape) (not= (:blend-mode shape) :normal)) + (obj/set! "mixBlendMode" (d/name (:blend-mode shape))))) include-metadata? (mf/use-ctx ed/include-metadata-ctx) @@ -91,7 +92,7 @@ svg-group? (propagate-wrapper-styles wrapper-props))] - [:& (mf/provider muc/render-ctx) {:value render-id} + [:& (mf/provider muc/render-id) {:value render-id} [:> :g wrapper-props (when include-metadata? [:& ed/export-data {:shape shape}]) diff --git a/frontend/src/app/main/ui/shapes/text/fo_text.cljs b/frontend/src/app/main/ui/shapes/text/fo_text.cljs index 158b6c117..eb5908c68 100644 --- a/frontend/src/app/main/ui/shapes/text/fo_text.cljs +++ b/frontend/src/app/main/ui/shapes/text/fo_text.cljs @@ -9,7 +9,6 @@ [app.common.colors :as clr] [app.common.data :as d] [app.common.geom.shapes :as gsh] - [app.main.ui.context :as muc] [app.main.ui.shapes.attrs :as attrs] [app.main.ui.shapes.text.styles :as sts] [app.util.color :as uc] @@ -91,23 +90,6 @@ (recur (uc/next-rgb current-rgb)) current-hex)))) -(defn- remap-colors - "Returns a new content replacing the original colors by their mapped 'simple color'" - [content color-mapping] - - (cond-> content - (and (:fill-opacity content) (< (:fill-opacity content) 1.0)) - (-> (assoc :fill-color (get color-mapping [(:fill-color content) (:fill-opacity content)])) - (assoc :fill-opacity 1.0)) - - (some? (:fill-color-gradient content)) - (-> (assoc :fill-color (get color-mapping (:fill-color-gradient content))) - (assoc :fill-opacity 1.0) - (dissoc :fill-color-gradient)) - - (contains? content :children) - (update :children #(mapv (fn [node] (remap-colors node color-mapping)) %)))) - (defn- fill->color "Given a content node returns the information about that node fill color" [{:keys [fill-color fill-opacity fill-color-gradient]}] @@ -199,13 +181,7 @@ ;; We add 8px to add a padding for the exporter ;; width (+ width 8) - [colors color-mapping color-mapping-inverse] (retrieve-colors shape) - - plain-colors? (mf/use-ctx muc/text-plain-colors-ctx) - - content (cond-> content - plain-colors? - (remap-colors color-mapping))] + [colors _color-mapping color-mapping-inverse] (retrieve-colors shape)] [:foreignObject {:x x diff --git a/frontend/src/app/main/ui/shapes/text/svg_text.cljs b/frontend/src/app/main/ui/shapes/text/svg_text.cljs index 06b0b50cc..62eb682b6 100644 --- a/frontend/src/app/main/ui/shapes/text/svg_text.cljs +++ b/frontend/src/app/main/ui/shapes/text/svg_text.cljs @@ -54,7 +54,7 @@ ::mf/wrap [mf/memo]} [props] - (let [render-id (mf/use-ctx muc/render-ctx) + (let [render-id (mf/use-ctx muc/render-id) shape (obj/get props "shape") shape (cond-> shape (:is-mask? shape) set-white-fill) @@ -109,6 +109,6 @@ (obj/set! "fill" (str "url(#fill-" index "-" render-id ")")))}) shape (assoc shape :fills (:fills data))] - [:& (mf/provider muc/render-ctx) {:key index :value (str render-id "_" (:id shape) "_" index)} + [:& (mf/provider muc/render-id) {:key index :value (str render-id "_" (:id shape) "_" index)} [:& shape-custom-strokes {:shape shape :position index :render-id render-id} [:> :text props (:text data)]]]))]])) diff --git a/frontend/src/app/main/ui/viewer.cljs b/frontend/src/app/main/ui/viewer.cljs index 1b35d882e..4191926df 100644 --- a/frontend/src/app/main/ui/viewer.cljs +++ b/frontend/src/app/main/ui/viewer.cljs @@ -26,7 +26,7 @@ [app.main.ui.static :as static] [app.main.ui.viewer.comments :refer [comments-layer comments-sidebar]] [app.main.ui.viewer.handoff :as handoff] - [app.main.ui.viewer.header :refer [header]] + [app.main.ui.viewer.header :as header] [app.main.ui.viewer.interactions :as interactions] [app.main.ui.viewer.login] [app.main.ui.viewer.share-link] @@ -36,8 +36,15 @@ [app.util.webapi :as wapi] [cuerdas.core :as str] [goog.events :as events] + [okulary.core :as l] [rumext.alpha :as mf])) +(def current-animation-ref + (l/derived :viewer-animation st/state)) + +(def current-overlays-ref + (l/derived :viewer-overlays st/state)) + (defn- calculate-size [objects frame zoom] (let [{:keys [x y width height]} (gsb/get-object-bounds objects frame)] @@ -60,7 +67,6 @@ :height (* height zoom) :vbox (str "0 0 " width " " height)}))) - (mf/defc viewer-pagination [{:keys [index num-frames left-bar right-bar] :as props}] [:* @@ -75,94 +81,132 @@ [:div.counter (str/join " / " [(+ index 1) num-frames])] [:span]]]) -(mf/defc viewer-wrapper - [{:keys [wrapper-size scroll orig-frame orig-viewport-ref orig-size page file users current-viewport-ref - size frame interactions-mode overlays zoom close-overlay section index] :as props}] - (let [{clist :list} (mf/deref refs/comments-local) - show-comments-list (and (= section :comments) (= :show clist))] +(mf/defc viewer-pagination-and-sidebar + {::mf/wrap [mf/memo]} + [{:keys [section index users frame page]}] + (let [comments-local (mf/deref refs/comments-local) + show-sidebar? (and (= section :comments) (:show-sidebar? comments-local))] [:* - [:& viewer-pagination {:index index :num-frames (count (:frames page)) :right-bar show-comments-list}] + [:& viewer-pagination + {:index index + :num-frames (count (:frames page)) + :right-bar show-sidebar?}] - (when show-comments-list - [:& comments-sidebar {:users users :frame frame :page page}]) + (when show-sidebar? + [:& comments-sidebar + {:users users + :frame frame + :page page}])])) - [:div.viewer-wrapper - {:style {:width (:width wrapper-size) - :height (:height wrapper-size)}} - [:& (mf/provider ctx/scroll-ctx) {:value @scroll} - [:div.viewer-clipper - [:* - (when orig-frame - [:div.viewport-container - {:ref orig-viewport-ref - :style {:width (:width orig-size) - :height (:height orig-size) - :position "relative"}} +(mf/defc viewer-overlay + [{:keys [overlay page frame zoom wrapper-size close-overlay interactions-mode]}] + (let [close-click-outside? (:close-click-outside overlay) + background-overlay? (:background-overlay overlay) + overlay-frame (:frame overlay) + overlay-position (:position overlay) - [:& interactions/viewport - {:frame orig-frame - :base-frame orig-frame - :frame-offset (gpt/point 0 0) - :size orig-size - :page page - :file file - :users users - :interactions-mode :hide}]]) + size + (mf/with-memo [page overlay zoom] + (calculate-size (:objects page) (:frame overlay) zoom)) - [:div.viewport-container - {:ref current-viewport-ref - :style {:width (:width size) - :height (:height size) - :position "relative"}} + on-click + (mf/use-fn + (mf/deps overlay close-overlay close-click-outside?) + (fn [_] + (when close-click-outside? + (close-overlay (:frame overlay)))))] - [:& interactions/viewport - {:frame frame - :base-frame frame - :frame-offset (gpt/point 0 0) - :size size - :page page - :file file - :users users - :interactions-mode interactions-mode}] + [:* + (when (or close-click-outside? background-overlay?) + [:div.viewer-overlay-background + {:class (dom/classnames :visible background-overlay?) + :style {:width (:width wrapper-size) + :height (:height wrapper-size) + :position "absolute" + :left 0 + :top 0} + :on-click on-click}]) - (for [overlay overlays] - (let [size-over (calculate-size (:objects page) (:frame overlay) zoom)] - [:* - (when (or (:close-click-outside overlay) - (:background-overlay overlay)) - [:div.viewer-overlay-background - {:class (dom/classnames - :visible (:background-overlay overlay)) - :style {:width (:width wrapper-size) - :height (:height wrapper-size) - :position "absolute" - :left 0 - :top 0} - :on-click #(when (:close-click-outside overlay) - (close-overlay (:frame overlay)))}]) - [:div.viewport-container.viewer-overlay + [:div.viewport-container.viewer-overlay + {:id (dm/str "overlay-" (:id overlay-frame)) + :style {:width (:width size) + :height (:height size) + :left (* (:x overlay-position) zoom) + :top (* (:y overlay-position) zoom)}} - {:id (str "overlay-" (-> overlay :frame :id)) - :style {:width (:width size-over) - :height (:height size-over) - :left (* (:x (:position overlay)) zoom) - :top (* (:y (:position overlay)) zoom)}} - [:& interactions/viewport - {:frame (:frame overlay) - :base-frame frame - :frame-offset (:position overlay) - :size size-over - :page page - :file file - :users users - :interactions-mode interactions-mode}]]]))]] + [:& interactions/viewport + {:frame overlay-frame + :base-frame frame + :frame-offset overlay-position + :size size + :page page + :interactions-mode interactions-mode}]]])) - (when (= section :comments) - [:& comments-layer {:file file - :users users - :frame frame - :page page - :zoom zoom}])]]]])) + +(mf/defc viewer-wrapper + [{:keys [wrapper-size orig-frame orig-viewport-ref orig-size page file users current-viewport-ref + size frame interactions-mode overlays zoom close-overlay section index] :as props}] + [:* + [:& viewer-pagination-and-sidebar + {:section section + :index index + :page page + :users users + :frame frame}] + + [:div.viewer-wrapper + {:style {:width (:width wrapper-size) + :height (:height wrapper-size)}} + [:div.viewer-clipper + (when orig-frame + [:div.viewport-container + {:ref orig-viewport-ref + :style {:width (:width orig-size) + :height (:height orig-size) + :position "relative"}} + + [:& interactions/viewport + {:frame orig-frame + :base-frame orig-frame + :frame-offset (gpt/point 0 0) + :size orig-size + :page page + :users users + :interactions-mode :hide}]]) + + [:div.viewport-container + {:ref current-viewport-ref + :style {:width (:width size) + :height (:height size) + :position "relative"}} + + [:& interactions/viewport + {:frame frame + :base-frame frame + :frame-offset (gpt/point 0 0) + :size size + :page page + :interactions-mode interactions-mode}] + + (for [overlay overlays] + [:& viewer-overlay {:overlay overlay + :key (dm/str (:id overlay)) + :page page + :frame frame + :zoom zoom + :wrapper-size wrapper-size + :close-overlay close-overlay + :interactions-mode interactions-mode}]) + + ]] + + + (when (= section :comments) + [:& comments-layer {:file file + :users users + :frame frame + :page page + :zoom zoom}])]]) (mf/defc viewer [{:keys [params data]}] @@ -184,10 +228,11 @@ local (mf/deref refs/viewer-local) nav-scroll (:nav-scroll local) - orig-viewport-ref (mf/use-ref nil) + orig-viewport-ref (mf/use-ref nil) current-viewport-ref (mf/use-ref nil) - viewer-section-ref (mf/use-ref nil) - current-animation (:current-animation local) + viewer-section-ref (mf/use-ref nil) + + current-animation (mf/deref current-animation-ref) page-id (or page-id (-> file :data :pages first)) @@ -208,26 +253,26 @@ frames (:frames page) frame (get frames index) - fullscreen? (mf/deref refs/viewer-fullscreen?) - overlays (:overlays local) - scroll (mf/use-state nil) + fullscreen? (mf/deref header/fullscreen-ref) + overlays (mf/deref current-overlays-ref) + scroll (mf/use-state nil) orig-frame (when (:orig-frame-id current-animation) (d/seek #(= (:id %) (:orig-frame-id current-animation)) frames)) - size (mf/use-memo - (mf/deps frame zoom) - (fn [] (calculate-size (:objects page) frame zoom))) + size + (mf/with-memo [frame zoom] + (calculate-size (:objects page) frame zoom)) - orig-size (mf/use-memo - (mf/deps orig-frame zoom) - (fn [] (when orig-frame - (calculate-size (:objects page) orig-frame zoom)))) + orig-size + (mf/with-memo [orig-frame zoom] + (when orig-frame + (calculate-size (:objects page) orig-frame zoom))) - wrapper-size (mf/use-memo - (mf/deps size orig-size zoom) - (fn [] (calculate-wrapper size orig-size zoom))) + wrapper-size + (mf/with-memo [size orig-size zoom] + (calculate-wrapper size orig-size zoom)) interactions-mode (:interactions-mode local) @@ -246,22 +291,23 @@ (dom/add-class! layout "force-visible")))))) on-click - (mf/use-callback + (mf/use-fn (mf/deps section) (fn [_] (when (= section :comments) (st/emit! (dcm/close-thread))))) set-up-new-size - (mf/use-callback + (mf/use-fn (fn [_] (let [viewer-section (dom/get-element "viewer-section") size (dom/get-client-size viewer-section)] (st/emit! (dv/set-viewport-size {:size size}))))) on-scroll - (fn [event] - (reset! scroll (dom/get-target-scroll event)))] + (mf/use-fn + (fn [event] + (reset! scroll (dom/get-target-scroll event))))] (hooks/use-shortcuts ::viewer sc/shortcuts) (when (nil? page) @@ -278,14 +324,13 @@ (let [name (:name file)] (dom/set-html-title (str "\u25b6 " (tr "title.viewer" name)))))) - (mf/use-effect - (fn [] - (dom/set-html-theme-color clr/gray-50 "dark") - (let [key1 (events/listen js/window "click" on-click) - key2 (events/listen (mf/ref-val viewer-section-ref) "scroll" on-scroll)] - (fn [] - (events/unlistenByKey key1) - (events/unlistenByKey key2))))) + (mf/with-effect [] + (dom/set-html-theme-color clr/gray-50 "dark") + (let [key1 (events/listen js/window "click" on-click) + key2 (events/listen (mf/ref-val viewer-section-ref) "scroll" on-scroll)] + (fn [] + (events/unlistenByKey key1) + (events/unlistenByKey key2)))) (mf/use-layout-effect (fn [] @@ -388,21 +433,24 @@ fonts (into #{} (keep :font-id) text-nodes)] (run! fonts/ensure-loaded! fonts)))) - [:div#viewer-layout {:class (dom/classnames - :force-visible (:show-thumbnails local) - :viewer-layout (not= section :handoff) - :handoff-layout (= section :handoff) - :fullscreen fullscreen?)} + [:div#viewer-layout + {:class (dom/classnames + :force-visible (:show-thumbnails local) + :viewer-layout (not= section :handoff) + :handoff-layout (= section :handoff) + :fullscreen fullscreen?)} [:div.viewer-content - [:& header {:project project - :index index - :file file - :page page - :frame frame - :permissions permissions - :zoom zoom - :section section}] + [:& header/header + {:project project + :index index + :file file + :page page + :frame frame + :permissions permissions + :zoom zoom + :section section}] + [:div.thumbnail-close {:on-click #(st/emit! dv/close-thumbnails-panel) :class (dom/classnames :invisible (not (:show-thumbnails local false)))}] [:& thumbnails-panel {:frames frames @@ -436,23 +484,24 @@ :index index :viewer-pagination viewer-pagination}] - [:& viewer-wrapper - {:wrapper-size wrapper-size - :scroll scroll - :orig-frame orig-frame - :orig-viewport-ref orig-viewport-ref - :orig-size orig-size - :page page - :file file - :users users - :current-viewport-ref current-viewport-ref - :size size - :frame frame - :interactions-mode interactions-mode - :overlays overlays - :zoom zoom - :section section - :index index}]))]]])) + [:& (mf/provider ctx/current-scroll) {:value @scroll} + [:& (mf/provider ctx/current-zoom) {:value zoom} + [:& viewer-wrapper + {:wrapper-size wrapper-size + :orig-frame orig-frame + :orig-viewport-ref orig-viewport-ref + :orig-size orig-size + :page page + :file file + :users users + :current-viewport-ref current-viewport-ref + :size size + :frame frame + :interactions-mode interactions-mode + :overlays overlays + :zoom zoom + :section section + :index index}]]]))]]])) ;; --- Component: Viewer Page diff --git a/frontend/src/app/main/ui/viewer/comments.cljs b/frontend/src/app/main/ui/viewer/comments.cljs index c46147ed9..8b1cf5c16 100644 --- a/frontend/src/app/main/ui/viewer/comments.cljs +++ b/frontend/src/app/main/ui/viewer/comments.cljs @@ -6,6 +6,7 @@ (ns app.main.ui.viewer.comments (:require + [app.common.data :as d] [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] @@ -23,132 +24,141 @@ [rumext.alpha :as mf])) (mf/defc comments-menu + {::mf/wrap [mf/memo] + ::mf/wrap-props false} [] - (let [{cmode :mode cshow :show clist :list} (mf/deref refs/comments-local) + (let [local (mf/deref refs/comments-local) + owner-filter (:owner-filter local) + status-filter (:status-filter local) + show-sidebar? (:show-sidebar? local) show-dropdown? (mf/use-state false) toggle-dropdown (mf/use-fn #(swap! show-dropdown? not)) hide-dropdown (mf/use-fn #(reset! show-dropdown? false)) - update-mode - (mf/use-callback - (fn [mode] - (st/emit! (dcm/update-filters {:mode mode})))) - - update-show - (mf/use-callback - (fn [mode] - (st/emit! (dcm/update-filters {:show mode})))) - - update-list - (mf/use-callback - (fn [show-list] - (st/emit! (dcm/update-filters {:list show-list}))))] + update-option (mf/use-fn + (fn [event] + (let [target (dom/get-current-target event) + key (d/read-string (dom/get-attribute target "data-key")) + val (d/read-string (dom/get-attribute target "data-val"))] + (st/emit! (dcm/update-options {key val})))))] [:div.view-options {:on-click toggle-dropdown} [:span.label (tr "labels.comments")] [:span.icon i/arrow-down] [:& dropdown {:show @show-dropdown? :on-close hide-dropdown} + [:ul.dropdown.with-check - [:li {:class (dom/classnames :selected (= :all cmode)) - :on-click #(update-mode :all)} + [:li {:class (dom/classnames :selected (= :all owner-filter)) + :data-key ":owner-filter" + :data-val ":all" + :on-click update-option} [:span.icon i/tick] [:span.label (tr "labels.show-all-comments")]] - [:li {:class (dom/classnames :selected (= :yours cmode)) - :on-click #(update-mode :yours)} + [:li {:class (dom/classnames :selected (= :yours owner-filter)) + :data-key ":owner-filter" + :data-val ":yours" + :on-click update-option} [:span.icon i/tick] [:span.label (tr "labels.show-your-comments")]] [:hr] - [:li {:class (dom/classnames :selected (= :pending cshow)) - :on-click #(update-show (if (= :pending cshow) :all :pending))} + [:li {:class (dom/classnames :selected (= :pending status-filter)) + :data-key ":status-filter" + :data-val (if (= :pending status-filter) ":all" ":pending") + :on-click update-option} [:span.icon i/tick] [:span.label (tr "labels.hide-resolved-comments")]] [:hr] - - [:li {:class (dom/classnames :selected (= :show clist)) - :on-click #(update-list (if (= :show clist) :hide :show))} + [:li {:class (dom/classnames :selected show-sidebar?) + :data-key ":show-sidebar?" + :data-val (if show-sidebar? "false" "true") + :on-click update-option} [:span.icon i/tick] [:span.label (tr "labels.show-comments-list")]]]]])) -(def threads-ref - (l/derived :comment-threads st/state)) - -(def comments-local-ref - (l/derived :comments-local st/state)) +(defn- update-thread-position [positions {:keys [id] :as thread}] + (if-let [data (get positions id)] + (-> thread + (assoc :position (:position data)) + (assoc :frame-id (:frame-id data))) + thread)) (mf/defc comments-layer [{:keys [zoom file users frame page] :as props}] - (let [profile (mf/deref refs/profile) - threads-position-ref (l/derived (l/in [:viewer :pages (:id page) :options :comment-threads-position]) st/state) - threads-position-map (mf/deref threads-position-ref) - threads-map (mf/deref threads-ref) + (let [profile (mf/deref refs/profile) + local (mf/deref refs/comments-local) - frame-corner (-> frame :points gsh/points->selrect gpt/point) - modifier1 (-> (gmt/matrix) - (gmt/translate (gpt/negate frame-corner))) + open-thread-id (:open local) + page-id (:id page) + file-id (:id file) + frame-id (:id frame) - modifier2 (-> (gpt/point frame-corner) - (gmt/translate-matrix)) + tpos-ref (mf/with-memo [page-id] + (-> (l/in [:pages page-id :options :comment-threads-position]) + (l/derived refs/viewer-data))) - cstate (mf/deref refs/comments-local) + positions (mf/deref tpos-ref) + threads-map (mf/deref refs/comment-threads) - update-thread-position (fn update-thread-position [thread] - (if (contains? threads-position-map (:id thread)) - (-> thread - (assoc :position (get-in threads-position-map [(:id thread) :position])) - (assoc :frame-id (get-in threads-position-map [(:id thread) :frame-id]))) - thread)) + frame-corner (mf/with-memo [frame] + (-> frame :points gsh/points->selrect gpt/point)) - threads (->> (vals threads-map) - (map update-thread-position) - (filter #(= (:frame-id %) (:id frame))) - (dcm/apply-filters cstate profile) - (filter (fn [{:keys [position]}] - (gsh/has-point? frame position)))) + modifier1 (mf/with-memo [frame-corner] + (-> (gmt/matrix) + (gmt/translate (gpt/negate frame-corner)))) + + modifier2 (mf/with-memo [frame-corner] + (-> (gpt/point frame-corner) + (gmt/translate-matrix))) + + threads (mf/with-memo [threads-map positions frame local profile] + (->> (vals threads-map) + (map (partial update-thread-position positions)) + (filter #(= (:frame-id %) (:id frame))) + (dcm/apply-filters local profile) + (filter (fn [{:keys [position]}] + (gsh/has-point? frame position))))) on-bubble-click - (mf/use-callback - (mf/deps cstate) - (fn [thread] - (if (= (:open cstate) (:id thread)) - (st/emit! (dcm/close-thread)) - (st/emit! (-> (dcm/open-thread thread) + (mf/use-fn + (mf/deps open-thread-id) + (fn [{:keys [id] :as thread}] + (st/emit! (if (= open-thread-id id) + (dcm/close-thread) + (-> (dcm/open-thread thread) (with-meta {::ev/origin "viewer"})))))) on-click - (mf/use-callback - (mf/deps cstate frame page file zoom) + (mf/use-fn + (mf/deps open-thread-id zoom page-id file-id modifier2) (fn [event] (dom/stop-propagation event) - (if (some? (:open cstate)) + (if (some? open-thread-id) (st/emit! (dcm/close-thread)) - (let [event (.-nativeEvent ^js event) - viewport-point (dom/get-offset-position event) - viewport-point (-> viewport-point (update :x #(/ % zoom)) (update :y #(/ % zoom))) - position (gpt/transform viewport-point modifier2) + (let [event (dom/event->native-event event) + position (-> (dom/get-offset-position event) + (update :x #(/ % zoom)) + (update :y #(/ % zoom)) + (gpt/transform modifier2)) params {:position position :page-id (:id page) :file-id (:id file)}] (st/emit! (dcm/create-draft params)))))) on-draft-cancel - (mf/use-callback - (mf/deps cstate) - #(st/emit! (dcm/close-thread))) + (mf/use-fn #(st/emit! (dcm/close-thread))) on-draft-submit - (mf/use-callback - (mf/deps frame) + (mf/use-fn + (mf/deps frame-id modifier2) (fn [draft] - (let [params (-> draft - (update :position gpt/transform modifier2) - (assoc :frame-id (:id frame)))] + (let [params (assoc draft :frame-id frame-id)] (st/emit! (dcm/create-thread-on-viewer params) (dcm/close-thread)))))] @@ -156,35 +166,37 @@ [:div.viewer-comments-container [:div.threads (for [item threads] - (let [item (update item :position gpt/transform modifier1)] - [:& cmt/thread-bubble {:thread item - :zoom zoom - :on-click on-bubble-click - :open? (= (:id item) (:open cstate)) - :key (:seqn item) - :origin :viewer}])) + [:& cmt/thread-bubble + {:thread item + :position-modifier modifier1 + :zoom zoom + :on-click on-bubble-click + :open? (= (:id item) (:open local)) + :key (:seqn item) + :origin :viewer}]) - (when-let [id (:open cstate)] - (when-let [thread (as-> (get threads-map id) $ - (when (some? $) - (update $ :position gpt/transform modifier1)))] - [:& cmt/thread-comments {:thread thread - :users users - :zoom zoom}])) + (when-let [thread (get threads-map open-thread-id)] + [:& cmt/thread-comments + {:thread thread + :position-modifier modifier1 + :users users + :zoom zoom}]) - (when-let [draft (:draft cstate)] - [:& cmt/draft-thread {:draft (update draft :position gpt/transform modifier1) - :on-cancel on-draft-cancel - :on-submit on-draft-submit - :zoom zoom}])]]])) + (when-let [draft (:draft local)] + [:& cmt/draft-thread + {:draft draft + :position-modifier modifier1 + :on-cancel on-draft-cancel + :on-submit on-draft-submit + :zoom zoom}])]]])) (mf/defc comments-sidebar [{:keys [users frame page]}] (let [profile (mf/deref refs/profile) - cstate (mf/deref refs/comments-local) - threads-map (mf/deref threads-ref) + local (mf/deref refs/comments-local) + threads-map (mf/deref refs/comment-threads) threads (->> (vals threads-map) - (dcm/apply-filters cstate profile) + (dcm/apply-filters local profile) (filter (fn [{:keys [position]}] (gsh/has-point? frame position))))] [:aside.settings-bar.settings-bar-right.comments-right-sidebar diff --git a/frontend/src/app/main/ui/viewer/handoff.cljs b/frontend/src/app/main/ui/viewer/handoff.cljs index 26a5a0faf..bb488fdba 100644 --- a/frontend/src/app/main/ui/viewer/handoff.cljs +++ b/frontend/src/app/main/ui/viewer/handoff.cljs @@ -6,13 +6,13 @@ (ns app.main.ui.viewer.handoff (:require - [app.main.data.viewer :as dv] + [app.main.data.viewer :as dv] [app.main.store :as st] [app.main.ui.viewer.handoff.left-sidebar :refer [left-sidebar]] [app.main.ui.viewer.handoff.render :refer [render-frame-svg]] [app.main.ui.viewer.handoff.right-sidebar :refer [right-sidebar]] [app.util.dom :as dom] - [app.util.keyboard :as kbd] + [app.util.keyboard :as kbd] [goog.events :as events] [rumext.alpha :as mf]) (:import goog.events.EventType)) diff --git a/frontend/src/app/main/ui/viewer/handoff/code.cljs b/frontend/src/app/main/ui/viewer/handoff/code.cljs index 030a443b4..265b2eba5 100644 --- a/frontend/src/app/main/ui/viewer/handoff/code.cljs +++ b/frontend/src/app/main/ui/viewer/handoff/code.cljs @@ -72,12 +72,7 @@ [:div.element-options [:div.code-block - [:div.code-row-lang - [:select.code-selection - [:option {:value "css"} "CSS"] - #_[:option {:value "sass"} "SASS"] - #_[:option {:value "less"} "Less"] - #_[:option {:value "stylus"} "Stylus"]] + [:div.code-row-lang "CSS" [:button.expand-button {:on-click on-expand } @@ -91,10 +86,7 @@ :code style-code}]]] [:div.code-block - [:div.code-row-lang - [:select.code-selection - [:option "SVG"] - [:option "HTML"]] + [:div.code-row-lang "SVG" [:button.expand-button {:on-click on-expand} diff --git a/frontend/src/app/main/ui/viewer/handoff/render.cljs b/frontend/src/app/main/ui/viewer/handoff/render.cljs index 3cfb179bf..61d982910 100644 --- a/frontend/src/app/main/ui/viewer/handoff/render.cljs +++ b/frontend/src/app/main/ui/viewer/handoff/render.cljs @@ -188,9 +188,8 @@ (mf/defc render-frame-svg [{:keys [page frame local size]}] - (let [objects (mf/use-memo - (mf/deps page frame) - (prepare-objects page frame size)) + (let [objects (mf/with-memo [page frame size] + (prepare-objects page frame size)) ;; Retrieve frame again with correct modifier frame (get objects (:id frame)) diff --git a/frontend/src/app/main/ui/viewer/header.cljs b/frontend/src/app/main/ui/viewer/header.cljs index 034473649..2fbf99e51 100644 --- a/frontend/src/app/main/ui/viewer/header.cljs +++ b/frontend/src/app/main/ui/viewer/header.cljs @@ -6,10 +6,10 @@ (ns app.main.ui.viewer.header (:require + [app.common.data.macros :as dm] [app.main.data.modal :as modal] [app.main.data.viewer :as dv] [app.main.data.viewer.shortcuts :as sc] - [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.components.dropdown :refer [dropdown]] [app.main.ui.export :refer [export-progress-widget]] @@ -19,8 +19,14 @@ [app.main.ui.viewer.interactions :refer [flows-menu interactions-menu]] [app.util.dom :as dom] [app.util.i18n :refer [tr]] + [okulary.core :as l] [rumext.alpha :as mf])) +(def fullscreen-ref + (l/derived (fn [state] + (dm/get-in state [:viewer-local :fullscreen?])) + st/state)) + (defn open-login-dialog [] (modal/show! :login-register {})) @@ -65,7 +71,7 @@ (mf/defc header-options [{:keys [section zoom page file index permissions]}] - (let [fullscreen? (mf/deref refs/viewer-fullscreen?) + (let [fullscreen? (mf/deref fullscreen-ref) toggle-fullscreen (mf/use-callback diff --git a/frontend/src/app/main/ui/viewer/interactions.cljs b/frontend/src/app/main/ui/viewer/interactions.cljs index 32c2a0c0d..aa479c9cb 100644 --- a/frontend/src/app/main/ui/viewer/interactions.cljs +++ b/frontend/src/app/main/ui/viewer/interactions.cljs @@ -17,6 +17,7 @@ [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.components.dropdown :refer [dropdown]] + [app.main.ui.hooks :as h] [app.main.ui.icons :as i] [app.main.ui.viewer.shapes :as shapes] [app.util.dom :as dom] @@ -27,84 +28,109 @@ (defn prepare-objects [page frame size] - (fn [] - (let [objects (:objects page) - frame-id (:id frame) - modifier (-> (gpt/point (:x size) (:y size)) - (gpt/negate) - (gmt/translate-matrix)) + (let [objects (:objects page) + frame-id (:id frame) + modifier (-> (gpt/point (:x size) (:y size)) + (gpt/negate) + (gmt/translate-matrix)) - update-fn #(d/update-when %1 %2 assoc-in [:modifiers :displacement] modifier)] + update-fn #(d/update-when %1 %2 assoc-in [:modifiers :displacement] modifier)] - (->> (cph/get-children-ids objects frame-id) - (into [frame-id]) - (reduce update-fn objects))))) + (->> (cph/get-children-ids objects frame-id) + (into [frame-id]) + (reduce update-fn objects)))) -(mf/defc viewport - {::mf/wrap [mf/memo]} - [{:keys [page interactions-mode frame base-frame frame-offset size]}] - (let [objects (mf/use-memo - (mf/deps page frame size) - (prepare-objects page frame size)) +(mf/defc viewport-svg + {::mf/wrap [mf/memo] + ::mf/wrap-props false} + [props] + (let [page (unchecked-get props "page") + frame (unchecked-get props "frame") + base (unchecked-get props "base") + offset (unchecked-get props "offset") + size (unchecked-get props "size") - wrapper (mf/use-memo - (mf/deps objects) - #(shapes/frame-container-factory objects)) + vbox (:vbox size) + + objects (mf/with-memo [page frame size] + (prepare-objects page frame size)) + + wrapper (mf/with-memo [objects] + (shapes/frame-container-factory objects)) ;; Retrieve frames again with correct modifier - frame (get objects (:id frame)) - base-frame (get objects (:id base-frame)) + frame (get objects (:id frame)) + base (get objects (:id base))] - on-click - (fn [_] - (when (= interactions-mode :show-on-click) - (st/emit! dv/flash-interactions))) - - on-mouse-wheel - (fn [event] - (when (kbd/mod? event) - (dom/prevent-default event) - (let [event (.getBrowserEvent ^js event) - delta (+ (.-deltaY ^js event) (.-deltaX ^js event))] - (if (pos? delta) - (st/emit! dv/decrease-zoom) - (st/emit! dv/increase-zoom))))) - - on-key-down - (fn [event] - (when (kbd/esc? event) - (st/emit! (dcm/close-thread))))] - - (mf/use-effect - (mf/deps interactions-mode) ;; on-click event depends on interactions-mode - (fn [] - ;; bind with passive=false to allow the event to be cancelled - ;; https://stackoverflow.com/a/57582286/3219895 - (let [key1 (events/listen goog/global "wheel" on-mouse-wheel #js {"passive" false}) - key2 (events/listen js/window "keydown" on-key-down) - key3 (events/listen js/window "click" on-click)] - (fn [] - (events/unlistenByKey key1) - (events/unlistenByKey key2) - (events/unlistenByKey key3))))) - - [:& (mf/provider shapes/base-frame-ctx) {:value base-frame} - [:& (mf/provider shapes/frame-offset-ctx) {:value frame-offset} - [:svg {:view-box (:vbox size) + [:& (mf/provider shapes/base-frame-ctx) {:value base} + [:& (mf/provider shapes/frame-offset-ctx) {:value offset} + [:svg {:view-box vbox :width (:width size) :height (:height size) :version "1.1" :xmlnsXlink "http://www.w3.org/1999/xlink" :xmlns "http://www.w3.org/2000/svg" :fill "none"} - [:& wrapper {:shape frame - :view-box (:vbox size)}]]]])) + [:& wrapper {:shape frame :view-box vbox}]]]])) +(mf/defc viewport + {::mf/wrap [mf/memo] + ::mf/wrap-props false} + [props] + (let [;; NOTE: with `use-equal-memo` hook we ensure that all values + ;; conserves the reference identity for avoid unnecesary dummy + ;; rerenders. + mode (h/use-equal-memo (unchecked-get props "interactions-mode")) + offset (h/use-equal-memo (unchecked-get props "frame-offset")) + size (h/use-equal-memo (unchecked-get props "size")) + + page (unchecked-get props "page") + frame (unchecked-get props "frame") + base (unchecked-get props "base-frame")] + + (mf/with-effect [mode] + (let [on-click + (fn [_] + (when (= mode :show-on-click) + (st/emit! (dv/flash-interactions)))) + + on-mouse-wheel + (fn [event] + (when (kbd/mod? event) + (dom/prevent-default event) + (let [event (dom/event->browser-event event) + delta (+ (.-deltaY ^js event) + (.-deltaX ^js event))] + (if (pos? delta) + (st/emit! dv/decrease-zoom) + (st/emit! dv/increase-zoom))))) + + on-key-down + (fn [event] + (when (kbd/esc? event) + (st/emit! (dcm/close-thread)))) + + + ;; bind with passive=false to allow the event to be cancelled + ;; https://stackoverflow.com/a/57582286/3219895 + key1 (events/listen goog/global "wheel" on-mouse-wheel #js {"passive" false}) + key2 (events/listen goog/global "keydown" on-key-down) + key3 (events/listen goog/global "click" on-click)] + (fn [] + (events/unlistenByKey key1) + (events/unlistenByKey key2) + (events/unlistenByKey key3)))) + + [:& viewport-svg {:page page + :frame frame + :base base + :offset offset + :size size}])) (mf/defc flows-menu {::mf/wrap [mf/memo]} [{:keys [page index]}] - (let [flows (get-in page [:options :flows]) + (let [flows (dm/get-in page [:options :flows]) frames (:frames page) frame (get frames index) current-flow (mf/use-state @@ -135,7 +161,6 @@ [:span.icon i/tick] [:span.label (:name flow)]])]]]))) - (mf/defc interactions-menu [] (let [local (mf/deref refs/viewer-local) diff --git a/frontend/src/app/main/ui/viewer/shapes.cljs b/frontend/src/app/main/ui/viewer/shapes.cljs index 2ffe4f186..ae280e215 100644 --- a/frontend/src/app/main/ui/viewer/shapes.cljs +++ b/frontend/src/app/main/ui/viewer/shapes.cljs @@ -206,19 +206,22 @@ :style {:pointer-events (when frame? "none")} :transform (gsh/transform-str shape)}]))) + +;; TODO: use-memo use-fn + (defn generic-wrapper-factory "Wrap some svg shape and add interaction controls" [component] (mf/fnc generic-wrapper {::mf/wrap-props false} [props] - (let [shape (unchecked-get props "shape") - childs (unchecked-get props "childs") - frame (unchecked-get props "frame") - objects (unchecked-get props "objects") - fixed? (unchecked-get props "fixed?") - delta (unchecked-get props "delta") - base-frame (mf/use-ctx base-frame-ctx) + (let [shape (unchecked-get props "shape") + childs (unchecked-get props "childs") + frame (unchecked-get props "frame") + objects (unchecked-get props "objects") + fixed? (unchecked-get props "fixed?") + delta (unchecked-get props "delta") + base-frame (mf/use-ctx base-frame-ctx) frame-offset (mf/use-ctx frame-offset-ctx) interactions-show? (mf/deref viewer-interactions-show?) @@ -226,20 +229,37 @@ interactions (:interactions shape) svg-element? (and (= :svg-raw (:type shape)) - (not= :svg (get-in shape [:content :tag])))] + (not= :svg (get-in shape [:content :tag]))) - (mf/use-effect - (fn [] - (let [sems (on-load shape base-frame frame-offset objects)] - #(run! tm/dispose! sems)))) + + on-mouse-down + (mf/use-fn (mf/deps shape base-frame frame-offset objects) + #(on-mouse-down % shape base-frame frame-offset objects)) + + on-mouse-up + (mf/use-fn (mf/deps shape base-frame frame-offset objects) + #(on-mouse-up % shape base-frame frame-offset objects)) + + on-mouse-enter + (mf/use-fn (mf/deps shape base-frame frame-offset objects) + #(on-mouse-enter % shape base-frame frame-offset objects)) + + on-mouse-leave + (mf/use-fn (mf/deps shape base-frame frame-offset objects) + #(on-mouse-leave % shape base-frame frame-offset objects))] + + + (mf/with-effect [] + (let [sems (on-load shape base-frame frame-offset objects)] + (partial run! tm/dispose! sems))) (if-not svg-element? [:> shape-container {:shape shape :cursor (when (ctsi/actionable? interactions) "pointer") - :on-mouse-down #(on-mouse-down % shape base-frame frame-offset objects) - :on-mouse-up #(on-mouse-up % shape base-frame frame-offset objects) - :on-mouse-enter #(on-mouse-enter % shape base-frame frame-offset objects) - :on-mouse-leave #(on-mouse-leave % shape base-frame frame-offset objects)} + :on-mouse-down on-mouse-down + :on-mouse-up on-mouse-up + :on-mouse-enter on-mouse-enter + :on-mouse-leave on-mouse-leave} [:& component {:shape shape :frame frame @@ -311,6 +331,7 @@ #js {:shape shape :childs childs :objects objects})] + [:> frame-wrapper props])))) (defn group-container-factory @@ -362,31 +383,43 @@ image-wrapper (image-wrapper) circle-wrapper (circle-wrapper)] (mf/fnc shape-container - {::mf/wrap-props false} + {::mf/wrap-props false + ::mf/wrap [mf/memo]} [props] - (let [scroll (mf/use-ctx ctx/scroll-ctx) - local (mf/deref refs/viewer-local) - zoom (:zoom local) - shape (unchecked-get props "shape") - parents (map (d/getf objects) (cph/get-parent-ids objects (:id shape))) - fixed? (or (:fixed-scroll shape) (some :fixed-scroll parents)) + (let [shape (unchecked-get props "shape") frame (unchecked-get props "frame") - delta {:x (/ (:scroll-left scroll) zoom) :y (/ (:scroll-top scroll) zoom)} + + ;; TODO: this watch of scroll position is killing + ;; performance of the viewer. + scroll (mf/use-ctx ctx/current-scroll) + zoom (mf/use-ctx ctx/current-zoom) + + fixed? (mf/with-memo [shape objects] + (->> (cph/get-parent-ids objects (:id shape)) + (map (d/getf objects)) + (concat [shape]) + (some :fixed-scroll))) + + delta {:x (/ (:scroll-left scroll) zoom) + :y (/ (:scroll-top scroll) zoom)} + group-container - (mf/use-memo (mf/deps objects) - #(group-container-factory objects)) + (mf/with-memo [objects] + (group-container-factory objects)) frame-container - (mf/use-memo (mf/deps objects) - #(frame-container-factory objects)) + (mf/with-memo [objects] + (frame-container-factory objects)) bool-container - (mf/use-memo (mf/deps objects) - #(bool-container-factory objects)) + (mf/with-memo [objects] + (bool-container-factory objects)) svg-raw-container - (mf/use-memo (mf/deps objects) - #(svg-raw-container-factory objects))] + (mf/with-memo [objects] + (svg-raw-container-factory objects)) + + ] (when (and shape (not (:hidden shape))) (let [shape (-> (gsh/transform-shape shape) (gsh/translate-to-frame frame) @@ -404,4 +437,3 @@ :group [:> group-container {:shape shape :frame frame :objects objects :fixed? fixed? :delta delta}] :bool [:> bool-container {:shape shape :frame frame :objects objects}] :svg-raw [:> svg-raw-container {:shape shape :frame frame :objects objects}]))))))) - diff --git a/frontend/src/app/main/ui/workspace/shapes.cljs b/frontend/src/app/main/ui/workspace/shapes.cljs index 293492c83..0c93d33ed 100644 --- a/frontend/src/app/main/ui/workspace/shapes.cljs +++ b/frontend/src/app/main/ui/workspace/shapes.cljs @@ -54,7 +54,7 @@ (mf/deps objects) #(cph/objects-by-frame objects))] - [:& (mf/provider ctx/active-frames-ctx) {:value active-frames} + [:& (mf/provider ctx/active-frames) {:value active-frames} ;; Render font faces only for shapes that are part of the root ;; frame but don't belongs to any other frame. (let [xform (comp @@ -79,7 +79,7 @@ (let [shape (obj/get props "shape") active-frames - (when (cph/root-frame? shape) (mf/use-ctx ctx/active-frames-ctx)) + (when (cph/root-frame? shape) (mf/use-ctx ctx/active-frames)) thumbnail? (and (some? active-frames) diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs index cbb4b3674..df3bea54e 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs @@ -909,6 +909,17 @@ (seq (:colors selected-assets)) (seq (:typographies selected-assets))) + extract-path-if-missing + (fn [graphic] + (let [[path name] (cph/parse-path-name (:name graphic))] + (if (and + (= (:name graphic) name) + (contains? graphic :path)) + graphic + (assoc graphic :path path :name name)))) + + objects (->> objects + (map extract-path-if-missing)) groups (group-assets objects reverse-sort?) @@ -1572,7 +1583,6 @@ (fn [event] (on-drop-asset-group event dragging? prefix selected-typographies-paths selected-typographies-full move-typography)))] - [:div {:on-drag-enter on-drag-enter :on-drag-leave on-drag-leave :on-drag-over on-drag-over @@ -1598,6 +1608,7 @@ [:div.drop-space]) (for [typography typographies] [:& typography-item {:typography typography + :key (dm/str (:id typography)) :file file :local? local? :handle-change handle-change @@ -1615,6 +1626,7 @@ (when-not (empty? path-item) [:& typographies-group {:file-id file-id :prefix (cph/merge-path-item prefix path-item) + :key (dm/str path-item) :groups content :open-groups open-groups :file file @@ -1642,7 +1654,9 @@ extract-path-if-missing (fn [typography] (let [[path name] (cph/parse-path-name (:name typography))] - (if (= (:name typography) name) + (if (and + (= (:name typography) name) + (contains? typography :path)) typography (assoc typography :path path :name name)))) @@ -1957,13 +1971,11 @@ (fn [asset-type asset-groups asset-id] (letfn [(flatten-groups [groups] - (concat - (get groups "" []) - (reduce concat - (into [] - (->> (filter #(seq (first %)) groups) - (map second) - (mapcat flatten-groups))))))] + (reduce concat [(get groups "" []) + (into [] + (->> (filter #(seq (first %)) groups) + (map second) + (mapcat flatten-groups)))]))] (let [selected-assets-type (get selected-assets asset-type) count-assets (count selected-assets-type)] (if (<= count-assets 0) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs index dd197446f..5a44676ba 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs @@ -174,7 +174,9 @@ (fn [shape] (if (ctsr/has-radius? shape) (update-fn shape) - shape))))) + shape)) + {:reg-objects? true + :attrs [:rx :ry :r1 :r2 :r3 :r4]}))) on-switch-to-radius-1 (mf/use-callback diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs index ff3d77aca..145ce30b9 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs @@ -467,27 +467,21 @@ (fn [event] (let [name (dom/get-target-val event)] (when-not (str/blank? name) - (on-change {:name name })))))] + (on-change {:name name})))))] - (mf/use-effect - (mf/deps editing?) - (fn [] - (when editing? - (reset! open? editing?)))) + (mf/with-effect [editing?] + (when editing? + (reset! open? editing?))) - (mf/use-effect - (mf/deps focus-name?) - (fn [] - (when focus-name? - (tm/schedule - #(when-let [node (mf/ref-val name-input-ref)] - (dom/focus! node) - (dom/select-text! node)))))) + (mf/with-effect [focus-name?] + (when focus-name? + (tm/schedule + #(when-let [node (mf/ref-val name-input-ref)] + (dom/focus! node) + (dom/select-text! node))))) - (mf/use-effect - (mf/deps on-change) - (fn [] - (mf/set-ref-val! on-change-ref {:on-change on-change}))) + (mf/with-effect [on-change] + (mf/set-ref-val! on-change-ref {:on-change on-change})) [:* [:div.element-set-options-group.typography-entry diff --git a/frontend/src/app/util/dom.cljs b/frontend/src/app/util/dom.cljs index c30e1d485..bd5bb883e 100644 --- a/frontend/src/app/util/dom.cljs +++ b/frontend/src/app/util/dom.cljs @@ -37,6 +37,14 @@ (when (some? e) (.-target e))) +(defn event->native-event + [^js e] + (.-nativeEvent e)) + +(defn event->browser-event + [^js e] + (.getBrowserEvent e)) + ;; --- New methods (declare get-elements-by-tag)