diff --git a/common/src/app/common/pages/helpers.cljc b/common/src/app/common/pages/helpers.cljc index 63f693195..b768680eb 100644 --- a/common/src/app/common/pages/helpers.cljc +++ b/common/src/app/common/pages/helpers.cljc @@ -7,6 +7,7 @@ (ns app.common.pages.helpers (:require [app.common.data :as d] + [app.common.data.macros :as dm] [app.common.geom.shapes :as gsh] [app.common.spec :as us] [app.common.spec.page :as spec.page] @@ -92,7 +93,7 @@ "Returns a vector of parents of the specified shape." [objects shape-id] (loop [result [] id shape-id] - (if-let [parent-id (->> id (get objects) :parent-id)] + (if-let [parent-id (dm/get-in objects [id :parent-id])] (recur (conj result parent-id) parent-id) result))) diff --git a/common/src/app/common/pages/indices.cljc b/common/src/app/common/pages/indices.cljc index 2ec512478..3a5a852ee 100644 --- a/common/src/app/common/pages/indices.cljc +++ b/common/src/app/common/pages/indices.cljc @@ -11,16 +11,17 @@ [app.common.uuid :as uuid] [clojure.set :as set])) -(defn calculate-frame-z-index [z-index frame-id objects] +(defn calculate-frame-z-index + [z-index frame-id base-idx objects] + (let [is-frame? (fn [id] (= :frame (get-in objects [id :type]))) - frame-shapes (->> objects (vals) (filterv #(= (:frame-id %) frame-id))) children (or (get-in objects [frame-id :shapes]) [])] (if (empty? children) z-index (loop [current (peek children) pending (pop children) - current-idx (count frame-shapes) + current-idx base-idx z-index z-index] (let [children (get-in objects [current :shapes]) @@ -46,10 +47,15 @@ [objects] (let [frames (cph/get-frames objects) - z-index (calculate-frame-z-index {} uuid/zero objects)] + + by-frame (cph/objects-by-frame objects) + frame-base-idx (d/update-vals by-frame count) + + z-index (calculate-frame-z-index {} uuid/zero (get frame-base-idx uuid/zero) objects)] (->> frames - (map :id) - (reduce #(calculate-frame-z-index %1 %2 objects) z-index)))) + (reduce + (fn [z-index {:keys [id]}] + (calculate-frame-z-index z-index id (get frame-base-idx id) objects)) z-index)))) (defn update-z-index "Updates the z-index given a set of ids to change and the old and new objects @@ -65,10 +71,13 @@ (map :id) (filter #(contains? changed-frames %))) - z-index (calculate-frame-z-index z-index uuid/zero new-objects)] + by-frame (cph/objects-by-frame new-objects) + frame-base-idx (d/update-vals by-frame count) + z-index (calculate-frame-z-index z-index uuid/zero (get frame-base-idx uuid/zero) new-objects)] (->> frames - (reduce #(calculate-frame-z-index %1 %2 new-objects) z-index)))) + (reduce (fn [z-index id] + (calculate-frame-z-index z-index id (get frame-base-idx id) new-objects)) z-index)))) (defn generate-child-parent-index [objects] @@ -84,10 +93,10 @@ (generate-child-all-parents-index objects (vals objects))) ([objects shapes] - (let [xf-parents (comp - (map :id) - (map #(vector % (cph/get-parent-ids objects %))))] - (into {} xf-parents shapes)))) + (let [shape->entry + (fn [shape] + [(:id shape) (cph/get-parent-ids objects (:id shape))])] + (into {} (map shape->entry) shapes)))) (defn create-clip-index "Retrieves the mask information for an object" diff --git a/frontend/resources/styles/main/partials/sidebar.scss b/frontend/resources/styles/main/partials/sidebar.scss index 452ee5220..9451071e1 100644 --- a/frontend/resources/styles/main/partials/sidebar.scss +++ b/frontend/resources/styles/main/partials/sidebar.scss @@ -125,25 +125,34 @@ width: 100%; height: 100%; display: grid; + align-items: center; grid-template-columns: auto 1fr auto; grid-column-gap: 8px; + cursor: pointer; & .back-button { - cursor: pointer; background: none; border: none; - transform: rotate(180deg); padding: 0; + margin: 0; + + & svg { + fill: $color-white; + transform: rotate(180deg); + margin-top: 3px; + } &:hover { svg { fill: $color-primary; } } + } - & svg { - fill: $color-white; - } + & .focus-name { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } & .focus-mode { diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index daabc9e74..768ca5b2b 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -115,17 +115,24 @@ (rx/filter (ptk/type? ::dwp/bundle-fetched)) (rx/take 1) (rx/map deref) - (rx/mapcat (fn [bundle] - (let [team-id (-> bundle :project :team-id)] - (rx/merge - (rx/of (dwn/initialize team-id file-id) - (dwp/initialize-file-persistence file-id) - (dwc/initialize-indices bundle)) + (rx/mapcat + (fn [bundle] + (rx/merge + (rx/of (dwc/initialize-indices bundle)) - (->> stream - (rx/filter #(= ::dwc/index-initialized %)) - (rx/take 1) - (rx/map #(file-initialized bundle)))))))))) + (->> (rx/of bundle) + (rx/mapcat + (fn [bundle] + (let [bundle (assoc bundle :file (t/decode-str (:file-raw bundle))) + team-id (dm/get-in bundle [:project :team-id])] + (rx/merge + (rx/of (dwn/initialize team-id file-id) + (dwp/initialize-file-persistence file-id)) + + (->> stream + (rx/filter #(= ::dwc/index-initialized %)) + (rx/take 1) + (rx/map #(file-initialized bundle)))))))))))))) ptk/EffectEvent (effect [_ _ _] diff --git a/frontend/src/app/main/data/workspace/common.cljs b/frontend/src/app/main/data/workspace/common.cljs index 3b7a99355..d50b29c2d 100644 --- a/frontend/src/app/main/data/workspace/common.cljs +++ b/frontend/src/app/main/data/workspace/common.cljs @@ -48,13 +48,12 @@ ;; --- Selection Index Handling (defn initialize-indices - [{:keys [file] :as bundle}] + [{:keys [file-raw] :as bundle}] (ptk/reify ::setup-selection-index ptk/WatchEvent (watch [_ _ _] (let [msg {:cmd :initialize-indices - :file-id (:id file) - :data (:data file)}] + :file-raw file-raw}] (->> (uw/ask! msg) (rx/map (constantly ::index-initialized))))))) diff --git a/frontend/src/app/main/data/workspace/persistence.cljs b/frontend/src/app/main/data/workspace/persistence.cljs index c27766a08..8bf0f2db4 100644 --- a/frontend/src/app/main/data/workspace/persistence.cljs +++ b/frontend/src/app/main/data/workspace/persistence.cljs @@ -251,8 +251,8 @@ (rp/query :project {:id project-id}) (rp/query :file-libraries {:file-id file-id})) (rx/take 1) - (rx/map (fn [[file users project libraries]] - {:file file + (rx/map (fn [[file-raw users project libraries]] + {:file-raw file-raw :users users :project project :libraries libraries})) diff --git a/frontend/src/app/main/data/workspace/selection.cljs b/frontend/src/app/main/data/workspace/selection.cljs index 5b4a2f000..d4ad40498 100644 --- a/frontend/src/app/main/data/workspace/selection.cljs +++ b/frontend/src/app/main/data/workspace/selection.cljs @@ -421,8 +421,11 @@ (assoc :position (if (= (:axis %) :x) (+ (:position %) (- (:x new-frame) (:x frame))) (+ (:position %) (- (:y new-frame) (:y frame))))))))] - (conj g - (into {} (map (juxt :id identity) new-guides))))) + + (if-not (empty? new-guides) + (conj g + (into {} (map (juxt :id identity) new-guides))) + {}))) guides frames)] (-> (pcb/with-page changes page) diff --git a/frontend/src/app/main/data/workspace/transforms.cljs b/frontend/src/app/main/data/workspace/transforms.cljs index f5a593240..8d1be936f 100644 --- a/frontend/src/app/main/data/workspace/transforms.cljs +++ b/frontend/src/app/main/data/workspace/transforms.cljs @@ -378,7 +378,7 @@ (update [_ state] (-> state (dissoc :workspace-modifiers) - (update :workspace-local dissoc :current-move-selected))))) + (dissoc ::current-move-selected))))) ;; -- Resize -------------------------------------------------------- @@ -721,15 +721,15 @@ ptk/UpdateEvent (update [_ state] - (if (nil? (get-in state [:workspace-local :current-move-selected])) + (if (nil? (get state ::current-move-selected)) (-> state (assoc-in [:workspace-local :transform] :move) - (assoc-in [:workspace-local :current-move-selected] same-event)) + (assoc ::current-move-selected same-event)) state)) ptk/WatchEvent (watch [_ state stream] - (if (= same-event (get-in state [:workspace-local :current-move-selected])) + (if (= same-event (get state ::current-move-selected)) (let [selected (wsh/lookup-selected state {:omit-blocked? true}) nudge (get-in state [:profile :props :nudge] {:big 10 :small 1}) move-events (->> stream @@ -744,10 +744,10 @@ (rx/concat (rx/merge (->> move-events - (rx/take-until stopper) (rx/scan #(gpt/add %1 mov-vec) (gpt/point 0 0)) (rx/map #(hash-map :displacement (gmt/translate-matrix %))) - (rx/map (partial set-modifiers selected))) + (rx/map (partial set-modifiers selected)) + (rx/take-until stopper)) (rx/of (move-selected direction shift?))) (rx/of (apply-modifiers selected) diff --git a/frontend/src/app/main/repo.cljs b/frontend/src/app/main/repo.cljs index 2e3ffb9fa..907020c7a 100644 --- a/frontend/src/app/main/repo.cljs +++ b/frontend/src/app/main/repo.cljs @@ -35,8 +35,7 @@ (rx/throw {:type :validation :code :request-body-too-large}) - (and (>= status 400) - (map? body)) + (and (>= status 400) (map? body)) (rx/throw body) :else @@ -49,13 +48,19 @@ (defn- send-query! "A simple helper for send and receive transit data on the penpot query api." - [id params] - (->> (http/send! {:method :get - :uri (u/join base-uri "api/rpc/query/" (name id)) - :credentials "include" - :query params}) - (rx/map http/conditional-decode-transit) - (rx/mapcat handle-response))) + ([id params] + (send-query! id params nil)) + + ([id params {:keys [raw-transit?]}] + (let [decode-transit (if raw-transit? + identity + (partial rx/map http/conditional-decode-transit))] + (->> (http/send! {:method :get + :uri (u/join base-uri "api/rpc/query/" (name id)) + :credentials "include" + :query params}) + (decode-transit) + (rx/mapcat handle-response))))) (defn- send-mutation! "A simple helper for a common case of sending and receiving transit @@ -77,6 +82,10 @@ [id params] (send-query! id params)) +(defmethod query :file + [id params] + (send-query! id params {:raw-transit? true})) + (defmethod mutation :default [id params] (send-mutation! id params)) diff --git a/frontend/src/app/main/ui/shapes/mask.cljs b/frontend/src/app/main/ui/shapes/mask.cljs index 1b192f76d..c8d518cf2 100644 --- a/frontend/src/app/main/ui/shapes/mask.cljs +++ b/frontend/src/app/main/ui/shapes/mask.cljs @@ -50,17 +50,17 @@ (let [mask (unchecked-get props "mask") render-id (mf/use-ctx muc/render-ctx) 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. fixed? (unchecked-get props "fixed?") delta (unchecked-get props "delta") - mask-for-bb (-> (gsh/transform-shape mask) - (cond-> fixed? (gsh/move delta))) + mask-bb (-> (gsh/transform-shape mask) + (cond-> fixed? (gsh/move delta)) + (:points)) - mask-bb (cond - svg-text? (gst/position-data-points mask-for-bb) - :else (:points mask-for-bb))] + mask-bb-rect (gsh/points->rect mask-bb)] [:defs [:filter {:id (filter-id render-id mask)} [:feFlood {:flood-color "white" @@ -81,7 +81,12 @@ ;; When te shape is a text we pass to the shape the info and disable the filter. ;; There is a bug in Firefox with filters and texts. We change the text to white at shape level [:mask {:class "mask-shape" - :id (mask-id render-id mask)} + :id (mask-id render-id mask) + :x (:x mask-bb-rect) + :y (:y mask-bb-rect) + :width (:width mask-bb-rect) + :height (:height mask-bb-rect) + :mask-units "userSpaceOnUse"} [:g {:filter (when-not svg-text? (filter-url render-id mask))} [:& shape-wrapper {:shape (-> mask (dissoc :shadow :blur) (assoc :is-mask? true))}]]]]))) diff --git a/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs b/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs index 2fde51491..531113297 100644 --- a/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs @@ -38,15 +38,13 @@ (mf/use-layout-effect (mf/deps transforms) (fn [] - (when (and (nil? @prev-transforms) - (some? transforms)) + (when (and (empty? @prev-modifiers) (d/not-empty? modifiers)) (utils/start-transform! node shapes)) - (when (some? modifiers) + (when (d/not-empty? modifiers) (utils/update-transform! node shapes transforms modifiers)) - (when (and (some? @prev-modifiers) - (empty? modifiers)) + (when (and (d/not-empty? @prev-modifiers) (empty? modifiers)) (utils/remove-transform! node @prev-shapes)) (reset! prev-modifiers modifiers) diff --git a/frontend/src/app/main/ui/workspace/sidebar/layers.cljs b/frontend/src/app/main/ui/workspace/sidebar/layers.cljs index 690287728..f1efac1d8 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/layers.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/layers.cljs @@ -498,11 +498,9 @@ [:div#layers.tool-window (if (d/not-empty? focus) [:div.tool-window-bar - [:div.focus-title - [:button.back-button - {:on-click #(st/emit! (dw/toggle-focus-mode))} - i/arrow-slide] - [:span (or title (tr "workspace.focus.selection"))] + [:div.focus-title {:on-click #(st/emit! (dw/toggle-focus-mode))} + [:button.back-button i/arrow-slide] + [:div.focus-name (or title (tr "workspace.focus.selection"))] [:div.focus-mode (tr "workspace.focus.focus-mode")]]] filter-component) diff --git a/frontend/src/app/main/ui/workspace/viewport/utils.cljs b/frontend/src/app/main/ui/workspace/viewport/utils.cljs index 03ee9365d..0c37143be 100644 --- a/frontend/src/app/main/ui/workspace/viewport/utils.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/utils.cljs @@ -150,10 +150,14 @@ (when (or (= (dom/get-tag-name node) "mask") (= (dom/get-tag-name node) "filter")) - (dom/set-attribute! node "data-old-x" (dom/get-attribute node "x")) - (dom/set-attribute! node "data-old-y" (dom/get-attribute node "y")) - (dom/set-attribute! node "data-old-width" (dom/get-attribute node "width")) - (dom/set-attribute! node "data-old-height" (dom/get-attribute node "height")))))))) + (let [old-x (dom/get-attribute node "x") + old-y (dom/get-attribute node "y") + old-width (dom/get-attribute node "width") + old-height (dom/get-attribute node "height")] + (dom/set-attribute! node "data-old-x" old-x) + (dom/set-attribute! node "data-old-y" old-y) + (dom/set-attribute! node "data-old-width" old-width) + (dom/set-attribute! node "data-old-height" old-height)))))))) (defn set-transform-att! [node att value] @@ -208,10 +212,19 @@ ;; The shape width/height will be automaticaly setup when the modifiers are applied nil + (or (= (dom/get-tag-name node) "mask") + (= (dom/get-tag-name node) "filter")) + (do + (dom/remove-attribute! node "data-old-x") + (dom/remove-attribute! node "data-old-y") + (dom/remove-attribute! node "data-old-width") + (dom/remove-attribute! node "data-old-height")) + :else (let [old-transform (dom/get-attribute node "data-old-transform")] - (when-not (some? old-transform) - (dom/remove-attribute! node "data-old-transform") + (if (some? old-transform) + (do (dom/remove-attribute! node "data-old-transform") + (dom/set-attribute! node "transform" old-transform)) (dom/remove-attribute! node "transform"))))))))) (defn format-viewbox [vbox] diff --git a/frontend/src/app/util/worker.cljs b/frontend/src/app/util/worker.cljs index 0ffa78d54..59d98f5b5 100644 --- a/frontend/src/app/util/worker.cljs +++ b/frontend/src/app/util/worker.cljs @@ -7,10 +7,10 @@ (ns app.util.worker "A lightweight layer on top of webworkers api." (:require - [app.common.transit :as t] [app.common.uuid :as uuid] [app.util.globals :refer [global]] [app.util.object :as obj] + [app.worker.messages :as wm] [beicon.core :as rx])) (declare handle-response) @@ -27,7 +27,7 @@ (rx/take-while #(not (:completed %)) ob) (rx/take 1 ob))) - data (t/encode-str message) + data (wm/encode message) instance (:instance worker)] (if (some? instance) @@ -71,11 +71,10 @@ handle-message (fn [event] - (let [data (.-data event) - data (t/decode-str data)] - (if (:error data) - (on-error (:error data)) - (rx/push! bus data)))) + (let [message (wm/decode (.-data event))] + (if (:error message) + (on-error (:error message)) + (rx/push! bus message)))) handle-error (fn [error] diff --git a/frontend/src/app/worker.cljs b/frontend/src/app/worker.cljs index 5889a8cd9..bb5ef3ff0 100644 --- a/frontend/src/app/worker.cljs +++ b/frontend/src/app/worker.cljs @@ -8,10 +8,10 @@ (:require [app.common.logging :as log] [app.common.spec :as us] - [app.common.transit :as t] [app.worker.export] [app.worker.impl :as impl] [app.worker.import] + [app.worker.messages :as wm] [app.worker.selection] [app.worker.snaps] [app.worker.thumbnails] @@ -46,7 +46,7 @@ [{:keys [sender-id payload] :as message}] (us/assert ::message message) (letfn [(post [msg] - (let [msg (-> msg (assoc :reply-to sender-id) (t/encode-str))] + (let [msg (-> msg (assoc :reply-to sender-id) (wm/encode))] (.postMessage js/self msg))) (reply [result] @@ -91,8 +91,8 @@ "Sends to the client a notification that its messages have been dropped" [{:keys [sender-id] :as message}] (us/assert ::message message) - (.postMessage js/self (t/encode-str {:reply-to sender-id - :dropped true}))) + (.postMessage js/self (wm/encode {:reply-to sender-id + :dropped true}))) (defn subscribe-buffer-messages "Creates a subscription to process the buffer messages" @@ -150,7 +150,7 @@ [event] (when (nil? (.-source event)) (let [message (.-data event) - message (t/decode-str message)] + message (wm/decode message)] (if (:buffer? message) (rx/push! buffer message) (handle-message message))))) diff --git a/frontend/src/app/worker/impl.cljs b/frontend/src/app/worker/impl.cljs index 977c8d85e..cfef2e3e2 100644 --- a/frontend/src/app/worker/impl.cljs +++ b/frontend/src/app/worker/impl.cljs @@ -7,6 +7,7 @@ (ns app.worker.impl (:require [app.common.pages.changes :as ch] + [app.common.transit :as t] [app.util.globals :refer [global]] [app.util.object :as obj] [okulary.core :as l])) @@ -28,14 +29,15 @@ message) (defmethod handler :initialize-indices - [{:keys [data] :as message}] + [{:keys [file-raw] :as message}] - (reset! state data) - - (handler (-> message - (assoc :cmd :selection/initialize-index))) - (handler (-> message - (assoc :cmd :snaps/initialize-index)))) + (let [data (-> (t/decode-str file-raw) :data) + message (assoc message :data data)] + (reset! state data) + (handler (-> message + (assoc :cmd :selection/initialize-index))) + (handler (-> message + (assoc :cmd :snaps/initialize-index))))) (defmethod handler :update-page-indices [{:keys [page-id changes] :as message}] diff --git a/frontend/src/app/worker/messages.cljs b/frontend/src/app/worker/messages.cljs new file mode 100644 index 000000000..87dc3b6c9 --- /dev/null +++ b/frontend/src/app/worker/messages.cljs @@ -0,0 +1,36 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) UXBOX Labs SL + +(ns app.worker.messages + "A lightweight layer on top of webworkers api." + (:require + [app.common.data :as d] + [app.common.transit :as t] + [app.util.object :as obj])) + +(defn encode [{:keys [sender-id reply-to payload buffer?] :as message}] + #js {:cmd (d/name (:cmd payload)) + :senderId (when sender-id (str sender-id)) + :replyTo (when reply-to (str reply-to)) + :payload (if (= :initialize-indices (:cmd payload)) + (:file-raw payload) + (when (some? payload) (t/encode-str payload))) + :buffer (when (some? buffer?) buffer?)}) + +(defn decode [^js data] + (let [cmd (obj/get data "cmd") + sender-id (obj/get data "senderId") + reply-to (obj/get data "replyTo") + payload (obj/get data "payload") + buffer (obj/get data "buffer")] + (d/without-nils + {:sender-id (when sender-id (uuid sender-id)) + :reply-to (when reply-to (uuid reply-to)) + :payload (if (= cmd "initialize-indices") + {:cmd :initialize-indices + :file-raw payload} + (when (some? payload) (t/decode-str payload))) + :buffer? buffer}))) diff --git a/frontend/src/app/worker/selection.cljs b/frontend/src/app/worker/selection.cljs index 74abc06dc..138c79b06 100644 --- a/frontend/src/app/worker/selection.cljs +++ b/frontend/src/app/worker/selection.cljs @@ -21,7 +21,7 @@ (defonce state (l/atom {})) -(defn index-shape +(defn make-index-shape [objects parents-index clip-parents-index] (fn [index shape] (let [{:keys [x y width height]} @@ -77,13 +77,16 @@ (let [shapes (-> objects (dissoc uuid/zero) vals) parents-index (cp/generate-child-all-parents-index objects) clip-parents-index (cp/create-clip-index objects parents-index) - bounds (-> objects objects-bounds add-padding-bounds) - index (reduce (index-shape objects parents-index clip-parents-index) - (qdt/create (clj->js bounds)) - shapes) + root-shapes (cph/get-immediate-children objects uuid/zero) + bounds (-> root-shapes gsh/selection-rect add-padding-bounds) - z-index (cp/calculate-z-index objects)] + index-shape (make-index-shape objects parents-index clip-parents-index) + initial-quadtree (qdt/create (clj->js bounds)) + + index (reduce index-shape initial-quadtree shapes) + + z-index (cp/calculate-z-index objects)] {:index index :z-index z-index :bounds bounds})) @@ -106,9 +109,8 @@ new-index (qdt/remove-all index changed-ids) - index (reduce (index-shape new-objects parents-index clip-parents-index) - new-index - shapes) + index-shape (make-index-shape new-objects parents-index clip-parents-index) + index (reduce index-shape new-index shapes) z-index (cp/update-z-index z-index changed-ids old-objects new-objects)]