diff --git a/CHANGES.md b/CHANGES.md index b4cdb7bf3d..7e1a1760b2 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -20,11 +20,13 @@ - Select through stroke only rectangle [Taiga #5484](https://tree.taiga.io/project/penpot/issue/5484) - Override browser Ctrl+ and Ctrl- zoom with Penpot Zoom [Taiga #3200](https://tree.taiga.io/project/penpot/us/3200) +- Reduce handlers for the flex layout gaps and paddings ### :bug: Bugs fixed -- Fix pixelated thumbnails [Github - #3681](https://github.com/penpot/penpot/issues/3681) [Github #3661](https://github.com/penpot/penpot/issues/3661) +- Fix pixelated thumbnails [Github #3681](https://github.com/penpot/penpot/issues/3681) [Github #3661](https://github.com/penpot/penpot/issues/3661) +- Fix problem with not applying colors to boards [Github #3941](https://github.com/penpot/penpot/issues/3941) +- Fix problem with path editor undoing changes [Github #3998](https://github.com/penpot/penpot/issues/3998) ### :arrow_up: Deps updates diff --git a/common/src/app/common/files/changes_builder.cljc b/common/src/app/common/files/changes_builder.cljc index fa35400bc8..edf5a0166c 100644 --- a/common/src/app/common/files/changes_builder.cljc +++ b/common/src/app/common/files/changes_builder.cljc @@ -465,7 +465,7 @@ {:type :add-obj :id id :page-id page-id - :parent-id (:frame-id shape) + :parent-id (:parent-id shape) :frame-id (:frame-id shape) :index (cfh/get-position-on-parent objects id) :obj (cond-> shape diff --git a/common/src/app/common/files/helpers.cljc b/common/src/app/common/files/helpers.cljc index 01507eea09..dce09d5dd5 100644 --- a/common/src/app/common/files/helpers.cljc +++ b/common/src/app/common/files/helpers.cljc @@ -72,13 +72,6 @@ (and (some? shape) (= :bool (dm/get-prop shape :type)))) -(defn group-like-shape? - ([objects id] - (group-like-shape? (get objects id))) - ([shape] - (or ^boolean (group-shape? shape) - ^boolean (bool-shape? shape)))) - (defn text-shape? [shape] (and (some? shape) @@ -123,6 +116,14 @@ ([shape] (d/not-empty? (:shapes shape)))) +(defn group-like-shape? + ([objects id] + (group-like-shape? (get objects id))) + ([shape] + (or ^boolean (group-shape? shape) + ^boolean (bool-shape? shape) + ^boolean (and (svg-raw-shape? shape) (has-children? shape))))) + ;; ---- ACCESSORS (defn get-children-ids diff --git a/common/src/app/common/files/libraries_helpers.cljc b/common/src/app/common/files/libraries_helpers.cljc index cf4b637c4f..bd750dee52 100644 --- a/common/src/app/common/files/libraries_helpers.cljc +++ b/common/src/app/common/files/libraries_helpers.cljc @@ -39,6 +39,7 @@ [it shapes objects page-id file-id components-v2 prepare-create-group prepare-create-board] (let [changes (pcb/empty-changes it page-id) + from-singe-frame? (and (= 1 (count shapes)) (-> shapes first cfh/frame-shape?)) [root changes old-root-ids] (if (and (= (count shapes) 1) (or (and (= (:type (first shapes)) :group) (not components-v2)) @@ -72,6 +73,15 @@ [root changes (map :id shapes)])) + changes + (cond-> changes + (not from-singe-frame?) + (pcb/update-shapes + (:shapes root) + (fn [shape] + (-> shape + (assoc :constraints-h :scale :constraints-v :scale))))) + objects' (assoc objects (:id root) root) [root-shape changes] (generate-add-component-changes changes root objects' file-id page-id components-v2) diff --git a/common/src/app/common/files/shapes_helpers.cljc b/common/src/app/common/files/shapes_helpers.cljc index 8a656886f1..03e3e89c1c 100644 --- a/common/src/app/common/files/shapes_helpers.cljc +++ b/common/src/app/common/files/shapes_helpers.cljc @@ -71,6 +71,23 @@ parent-id (or parent-id (dm/get-in objects [selected-id :parent-id])) base-parent (get objects parent-id) + layout-props + (when (and (= 1 (count selected)) + (ctl/any-layout? base-parent)) + (let [shape (get objects selected-id)] + (select-keys shape ctl/layout-item-props))) + + target-cell-id + (if (and (nil? target-cell-id) + (ctl/grid-layout? objects parent-id)) + ;; Find the top-left grid cell of the selected elements + (let [ncols (count (:layout-grid-columns base-parent))] + (->> selected + (map #(ctl/get-cell-by-shape-id base-parent %)) + (apply min-key (fn [{:keys [row column]}] (+ (* ncols row) column))) + :id)) + target-cell-id) + attrs {:type :frame :x (:x srect) :y (:y srect) @@ -90,12 +107,14 @@ :parent-id parent-id :shapes (into [] selected)) - :always - (with-meta {:index new-index}) + (some? layout-props) + (d/patch-object layout-props) (or (not= frame-id uuid/zero) without-fill?) (assoc :fills [] :hide-in-viewer true))) + shape (with-meta shape {:index new-index}) + [shape changes] (prepare-add-shape changes shape objects) @@ -105,15 +124,23 @@ changes (cond-> changes (ctl/grid-layout? objects (:parent-id shape)) - (-> (cond-> (some? target-cell-id) - (pcb/update-shapes - [(:parent-id shape)] - (fn [parent] - (-> parent - (assoc :layout-grid-cells (:layout-grid-cells base-parent)) - (assoc-in [:layout-grid-cells target-cell-id :shapes] [id]) - (assoc :position :auto))))) - (pcb/update-shapes [(:parent-id shape)] ctl/assign-cells {:with-objects? true}) + (-> (pcb/update-shapes + [(:parent-id shape)] + (fn [parent objects] + ;; This restores the grid layout before adding and moving the shapes + ;; this is done because the add+move could have altered the layout and we + ;; want to do it after both operations are completed. Also here we could + ;; asign the new element to a target-cell + (-> parent + (assoc :layout-grid-cells (:layout-grid-cells base-parent)) + (assoc :layout-grid-rows (:layout-grid-rows base-parent)) + (assoc :layout-grid-columns (:layout-grid-columns base-parent)) + + (cond-> (some? target-cell-id) + (assoc-in [:layout-grid-cells target-cell-id :shapes] [(:id shape)])) + (ctl/assign-cells objects))) + {:with-objects? true}) + (pcb/reorder-grid-children [(:parent-id shape)])))] [shape changes]))))) diff --git a/common/src/app/common/geom/shapes/flex_layout/params.cljc b/common/src/app/common/geom/shapes/flex_layout/params.cljc index 4293dd53de..7bd1ce855f 100644 --- a/common/src/app/common/geom/shapes/flex_layout/params.cljc +++ b/common/src/app/common/geom/shapes/flex_layout/params.cljc @@ -19,7 +19,9 @@ ([objects shapes parent] (when (d/not-empty? shapes) - (let [points + (let [shapes (->> shapes (remove :hidden)) + + points (->> shapes (map :id) (ctt/sort-z-index objects) diff --git a/common/src/app/common/geom/shapes/grid_layout/params.cljc b/common/src/app/common/geom/shapes/grid_layout/params.cljc index 16fcfd426b..befd93e688 100644 --- a/common/src/app/common/geom/shapes/grid_layout/params.cljc +++ b/common/src/app/common/geom/shapes/grid_layout/params.cljc @@ -117,7 +117,8 @@ :layout-grid-rows [ctl/default-track-value ctl/default-track-value]} (ctl/create-cells [1 1 2 2])) - (let [all-shapes-rect (gco/shapes->rect shapes) + (let [shapes (->> shapes (remove :hidden)) + all-shapes-rect (gco/shapes->rect shapes) shapes+bounds (->> shapes (map #(vector % (grc/points->rect (get % :points))))) diff --git a/common/src/app/common/geom/shapes/transforms.cljc b/common/src/app/common/geom/shapes/transforms.cljc index 6adf456cd4..f9be02adaf 100644 --- a/common/src/app/common/geom/shapes/transforms.cljc +++ b/common/src/app/common/geom/shapes/transforms.cljc @@ -414,7 +414,11 @@ (gco/transform-points shape-center (:transform group (gmt/matrix)))) ;; Calculate the new selrect - new-selrect (grc/points->rect base-points)] + sr-transform (gmt/transform-in (gco/points->center new-points) (:transform-inverse group (gmt/matrix))) + new-selrect + (-> new-points + (gco/transform-points sr-transform) + (grc/points->rect))] ;; Updates the shape and the applytransform-rect will update the other properties (-> group diff --git a/common/src/app/common/types/component.cljc b/common/src/app/common/types/component.cljc index 9dd0a8c80f..67ffd24147 100644 --- a/common/src/app/common/types/component.cljc +++ b/common/src/app/common/types/component.cljc @@ -83,18 +83,19 @@ :layout-grid-dir :layout-container :layout-grid-rows :layout-container :layout-grid-columns :layout-container - :layout-grid-cells :layout-container - - :layout-item-margin :layout-item - :layout-item-margin-type :layout-item - :layout-item-h-sizing :layout-item - :layout-item-v-sizing :layout-item - :layout-item-max-h :layout-item - :layout-item-min-h :layout-item - :layout-item-max-w :layout-item - :layout-item-min-w :layout-item - :layout-item-align-self :layout-item}) + :layout-grid-cells :layout-container}) +(def swap-keep-attrs + [:layout-item-margin + :layout-item-margin-type + :layout-item-h-sizing + :layout-item-v-sizing + :layout-item-max-h + :layout-item-min-h + :layout-item-max-w + :layout-item-min-w + :layout-item-absolute + :layout-item-z-index]) (defn instance-root? "Check if this shape is the head of a top instance." diff --git a/common/src/app/common/types/container.cljc b/common/src/app/common/types/container.cljc index f70705230b..b00a41b1a4 100644 --- a/common/src/app/common/types/container.cljc +++ b/common/src/app/common/types/container.cljc @@ -6,6 +6,7 @@ (ns app.common.types.container (:require + [app.common.data :as d] [app.common.data.macros :as dm] [app.common.files.helpers :as cfh] [app.common.geom.point :as gpt] @@ -287,7 +288,8 @@ component-shape (if components-v2 (-> (get-shape component-page (:main-instance-id component)) (assoc :parent-id nil) ;; On v2 we force parent-id to nil in order to behave like v1 - (assoc :frame-id uuid/zero)) + (assoc :frame-id uuid/zero) + (d/without-keys ctk/swap-keep-attrs)) (get-shape component (:id component))) orig-pos (gpt/point (:x component-shape) (:y component-shape)) diff --git a/common/src/app/common/types/shape/layout.cljc b/common/src/app/common/types/shape/layout.cljc index 5a48aa0162..104fb9bc71 100644 --- a/common/src/app/common/types/shape/layout.cljc +++ b/common/src/app/common/types/shape/layout.cljc @@ -73,6 +73,18 @@ (def justify-items-types #{:start :end :center :stretch}) +(def layout-item-props + [:layout-item-margin + :layout-item-margin-type + :layout-item-h-sizing + :layout-item-v-sizing + :layout-item-max-h + :layout-item-min-h + :layout-item-max-w + :layout-item-min-w + :layout-item-absolute + :layout-item-z-index]) + (sm/def! ::layout-attrs [:map {:title "LayoutAttrs"} [:layout {:optional true} [::sm/one-of layout-types]] diff --git a/frontend/resources/images/cap-circle-marker.svg b/frontend/resources/images/cap-circle-marker.svg deleted file mode 100644 index b41388e506..0000000000 --- a/frontend/resources/images/cap-circle-marker.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/frontend/resources/images/cap-diamond-marker.svg b/frontend/resources/images/cap-diamond-marker.svg deleted file mode 100644 index 0e23183406..0000000000 --- a/frontend/resources/images/cap-diamond-marker.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/frontend/resources/images/cap-line-arrow.svg b/frontend/resources/images/cap-line-arrow.svg deleted file mode 100644 index 0df0673ba7..0000000000 --- a/frontend/resources/images/cap-line-arrow.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/frontend/resources/images/cap-round.svg b/frontend/resources/images/cap-round.svg deleted file mode 100644 index 594e02575f..0000000000 --- a/frontend/resources/images/cap-round.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/frontend/resources/images/cap-square-marker.svg b/frontend/resources/images/cap-square-marker.svg deleted file mode 100644 index 2340ce571b..0000000000 --- a/frontend/resources/images/cap-square-marker.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/frontend/resources/images/cap-square.svg b/frontend/resources/images/cap-square.svg deleted file mode 100644 index a2e3b6260b..0000000000 --- a/frontend/resources/images/cap-square.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/frontend/resources/images/cap-triangle-arrow.svg b/frontend/resources/images/cap-triangle-arrow.svg deleted file mode 100644 index f294d01cf5..0000000000 --- a/frontend/resources/images/cap-triangle-arrow.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/frontend/resources/images/icons/cap-circle-marker.svg b/frontend/resources/images/icons/cap-circle-marker.svg new file mode 100644 index 0000000000..3068ad7b7e --- /dev/null +++ b/frontend/resources/images/icons/cap-circle-marker.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/cap-diamond-marker.svg b/frontend/resources/images/icons/cap-diamond-marker.svg new file mode 100644 index 0000000000..0391c0ffea --- /dev/null +++ b/frontend/resources/images/icons/cap-diamond-marker.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/cap-line-arrow.svg b/frontend/resources/images/icons/cap-line-arrow.svg new file mode 100644 index 0000000000..c24e84233d --- /dev/null +++ b/frontend/resources/images/icons/cap-line-arrow.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/cap-round.svg b/frontend/resources/images/icons/cap-round.svg new file mode 100644 index 0000000000..c717c13b90 --- /dev/null +++ b/frontend/resources/images/icons/cap-round.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/cap-square-marker.svg b/frontend/resources/images/icons/cap-square-marker.svg new file mode 100644 index 0000000000..00407fd379 --- /dev/null +++ b/frontend/resources/images/icons/cap-square-marker.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/cap-square.svg b/frontend/resources/images/icons/cap-square.svg new file mode 100644 index 0000000000..24ad4f43f4 --- /dev/null +++ b/frontend/resources/images/icons/cap-square.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/cap-triangle-arrow.svg b/frontend/resources/images/icons/cap-triangle-arrow.svg new file mode 100644 index 0000000000..6d6f159783 --- /dev/null +++ b/frontend/resources/images/icons/cap-triangle-arrow.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/styles/common/refactor/z-index.scss b/frontend/resources/styles/common/refactor/z-index.scss index fed8066ce4..5be8e2e5bf 100644 --- a/frontend/resources/styles/common/refactor/z-index.scss +++ b/frontend/resources/styles/common/refactor/z-index.scss @@ -5,7 +5,7 @@ // Copyright (c) KALEIDOS INC $z-index-1: 1; // floating elements -$z-index-2: 2; //sidebars +$z-index-2: 2; // sidebars $z-index-3: 3; // context menu $z-index-4: 4; // modal $z-index-10: 10; diff --git a/frontend/resources/styles/main/partials/sidebar.scss b/frontend/resources/styles/main/partials/sidebar.scss index 88b199e3e5..ce901a6a4d 100644 --- a/frontend/resources/styles/main/partials/sidebar.scss +++ b/frontend/resources/styles/main/partials/sidebar.scss @@ -159,11 +159,6 @@ } } } - - .assets-bar .tool-window { - flex: none; - height: auto; - } } .empty { diff --git a/frontend/src/app/main.cljs b/frontend/src/app/main.cljs index 7ed4bd059e..868d4b9ea8 100644 --- a/frontend/src/app/main.cljs +++ b/frontend/src/app/main.cljs @@ -117,17 +117,17 @@ (st/emit! (initialize))) (defn ^:export reinit - [] - ;; NOTE: in cases of some strange behavior after hot-reload, - ;; uncomment this lines; they make a hard-rerender instead - ;; soft-rerender. - ;; - ;; (mf/unmount! app-root) - ;; (mf/unmount! modal-root) - ;; (set! app-root (mf/create-root (dom/get-element "app"))) - ;; (set! modal-root (mf/create-root (dom/get-element "modal"))) - (st/emit! (ev/initialize)) - (init-ui)) + ([] + (reinit false)) + ([hard?] + ;; The hard flag will force to unmount the whole UI and will redraw every component + (when hard? + (mf/unmount! app-root) + (mf/unmount! modal-root) + (set! app-root (mf/create-root (dom/get-element "app"))) + (set! modal-root (mf/create-root (dom/get-element "modal")))) + (st/emit! (ev/initialize)) + (init-ui))) (defn ^:dev/after-load after-load [] diff --git a/frontend/src/app/main/data/dashboard.cljs b/frontend/src/app/main/data/dashboard.cljs index 10effaf162..c782b49fae 100644 --- a/frontend/src/app/main/data/dashboard.cljs +++ b/frontend/src/app/main/data/dashboard.cljs @@ -320,7 +320,9 @@ (update [_ state] (update state :dashboard-local assoc :selected-files #{} - :selected-project nil)))) + :selected-project nil + :menu-open false + :menu-pos nil)))) (defn toggle-file-select [{:keys [id project-id] :as file}] @@ -339,6 +341,53 @@ (assoc :selected-project project-id)))) state))))) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Show grid menu +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn show-file-menu-with-position + [file-id pos] + (ptk/reify ::show-file-menu-with-position + ptk/UpdateEvent + (update [_ state] + (update state :dashboard-local + assoc :menu-open true + :menu-pos pos + :file-id file-id)))) + +(defn show-file-menu + [] + (ptk/reify ::show-file-menu + ptk/UpdateEvent + (update [_ state] + (update state :dashboard-local + assoc :menu-open true)))) + +(defn hide-file-menu + [] + (ptk/reify ::hide-file-menu + ptk/UpdateEvent + (update [_ state] + (update state :dashboard-local + assoc :menu-open false)))) + +(defn start-edit-file-name + [file-id] + (ptk/reify ::start-edit-file-menu + ptk/UpdateEvent + (update [_ state] + (update state :dashboard-local + assoc :edition true + :file-id file-id)))) + +(defn stop-edit-file-name + [] + (ptk/reify ::stop-edit-file-name + ptk/UpdateEvent + (update [_ state] + (update state :dashboard-local + assoc :edition false)))) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Data Modification ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/frontend/src/app/main/data/workspace/colors.cljs b/frontend/src/app/main/data/workspace/colors.cljs index e20b9019b7..2dd79469e7 100644 --- a/frontend/src/app/main/data/workspace/colors.cljs +++ b/frontend/src/app/main/data/workspace/colors.cljs @@ -12,7 +12,6 @@ [app.common.files.helpers :as cfh] [app.common.schema :as sm] [app.common.text :as txt] - [app.common.types.component :as ctk] [app.main.broadcast :as mbc] [app.main.data.events :as ev] [app.main.data.modal :as md] @@ -427,11 +426,7 @@ (if (empty? pending) result (let [cur (first pending) - ;; We treat frames that aren't components and with no fill the same as groups - group? (or (cfh/group-shape? objects cur) - (and (cfh/frame-shape? objects cur) - (empty? (dm/get-in objects [cur :fills])) - (not (ctk/instance-head? (get objects cur))))) + group? (cfh/group-shape? objects cur) pending (if group? diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index ae2d666e88..54047f0e3c 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -829,7 +829,7 @@ 0))))) (defn- add-component-for-swap - [shape file-id id-new-component index target-cell] + [shape file-id id-new-component index target-cell keep-props-values] (dm/assert! (uuid? id-new-component)) (dm/assert! (uuid? file-id)) (ptk/reify ::add-component-for-swap @@ -856,8 +856,13 @@ (:parent-id shape) (:frame-id shape)) - ;; We need to set the same index as the original shape - changes (pcb/change-parent changes (:parent-id shape) [new-shape] index {:component-swap true})] + changes + (-> changes + ;; Restore the properties + (pcb/update-shapes [(:id new-shape)] #(d/patch-object % keep-props-values)) + + ;; We need to set the same index as the original shape + (pcb/change-parent (:parent-id shape) [new-shape] index {:component-swap true}))] ;; First delete so we don't break the grid layout cells (rx/of (dch/commit-changes changes) @@ -880,9 +885,12 @@ target-cell (when (ctl/grid-layout? parent) (ctl/get-cell-by-shape-id parent (:id shape))) - index (find-shape-index objects (:parent-id shape) (:id shape))] + index (find-shape-index objects (:parent-id shape) (:id shape)) + + ;; Store the properties that need to be maintained when the component is swapped + keep-props-values (select-keys shape ctk/swap-keep-attrs)] (rx/of (dwsh/delete-shapes nil (d/ordered-set (:id shape)) {:component-swap true}) - (add-component-for-swap shape file-id id-new-component index target-cell) + (add-component-for-swap shape file-id id-new-component index target-cell keep-props-values) (ptk/data-event :layout/update [(:parent-id shape)])))))) (defn component-multi-swap @@ -1002,12 +1010,16 @@ (rx/of (dch/commit-changes (assoc changes ;; TODO a ver qué pasa con esto :file-id file-id)))) (when-not (empty? updated-frames) - (->> (rx/from updated-frames) - (rx/mapcat (fn [shape] - (rx/of - (dwt/clear-thumbnail file-id (:page-id shape) (:id shape) "frame") - (when-not (= (:frame-id shape) uuid/zero) - (dwt/clear-thumbnail file-id (:page-id shape) (:frame-id shape) "frame"))))))) + (rx/merge + (rx/of (ptk/data-event :layout/update (map :id updated-frames))) + (->> (rx/from updated-frames) + (rx/mapcat + (fn [shape] + (rx/of + (dwt/clear-thumbnail file-id (:page-id shape) (:id shape) "frame") + (when-not (= (:frame-id shape) uuid/zero) + (dwt/clear-thumbnail file-id (:page-id shape) (:frame-id shape) "frame")))))))) + (when (not= file-id library-id) ;; When we have just updated the library file, give some time for the ;; update to finish, before marking this file as synced. @@ -1054,8 +1066,7 @@ ignore-until (dm/get-in state [:workspace-file :ignore-sync-until]) libraries-need-sync (filter #(seq (assets-need-sync % file-data ignore-until)) (vals (get state :workspace-libraries))) - do-more-info #(do (modal/show! :libraries-dialog {:starting-tab :updates}) - (st/emit! msg/hide)) + do-more-info #(modal/show! :libraries-dialog {:starting-tab :updates}) do-update #(do (apply st/emit! (map (fn [library] (sync-file (:current-file-id state) (:id library))) diff --git a/frontend/src/app/main/data/workspace/path/common.cljs b/frontend/src/app/main/data/workspace/path/common.cljs index 6892484127..8edd06ffe6 100644 --- a/frontend/src/app/main/data/workspace/path/common.cljs +++ b/frontend/src/app/main/data/workspace/path/common.cljs @@ -7,6 +7,7 @@ (ns app.main.data.workspace.path.common (:require [app.common.schema :as sm] + [app.common.svg.path.subpath :as ups] [app.main.data.workspace.path.state :as st] [potok.v2.core :as ptk])) @@ -52,10 +53,11 @@ (dissoc state :last-point :prev-handler :drag-handler :preview)) (defn finish-path - [_source] + [] (ptk/reify ::finish-path ptk/UpdateEvent (update [_ state] (let [id (st/get-path-id state)] (-> state - (update-in [:workspace-local :edit-path id] clean-edit-state)))))) + (update-in [:workspace-local :edit-path id] clean-edit-state) + (update-in (st/get-path-location state :content) ups/close-subpaths)))))) diff --git a/frontend/src/app/main/data/workspace/path/drawing.cljs b/frontend/src/app/main/data/workspace/path/drawing.cljs index 64007cc8fa..2e926e3154 100644 --- a/frontend/src/app/main/data/workspace/path/drawing.cljs +++ b/frontend/src/app/main/data/workspace/path/drawing.cljs @@ -153,7 +153,7 @@ drag-events-stream (rx/of (finish-drag)) (rx/of (close-path-drag-end)))) - (rx/of (common/finish-path "close-path"))))))) + (rx/of (common/finish-path))))))) (defn close-path-drag-end [] (ptk/reify ::close-path-drag-end @@ -253,7 +253,7 @@ (rx/of (common/init-path)) (rx/merge mousemove-events mousedown-events) - (rx/of (common/finish-path "after-events"))))))) + (rx/of (common/finish-path))))))) (defn setup-frame [] (ptk/reify ::setup-frame @@ -362,7 +362,7 @@ (not= content old-content) (rx/of (changes/save-path-content) (start-draw-mode)) (= mode :draw) (rx/of :interrupt) - :else (rx/of (common/finish-path "changed-content"))))))) + :else (rx/of (common/finish-path))))))) (defn change-edit-mode [mode] (ptk/reify ::change-edit-mode @@ -376,7 +376,7 @@ (watch [_ state _] (let [id (st/get-path-id state)] (cond - (and id (= :move mode)) (rx/of (common/finish-path "change-edit-mode")) + (and id (= :move mode)) (rx/of (common/finish-path)) (and id (= :draw mode)) (rx/of (dwc/hide-toolbar) (start-draw-mode)) :else (rx/empty)))))) diff --git a/frontend/src/app/main/data/workspace/path/helpers.cljs b/frontend/src/app/main/data/workspace/path/helpers.cljs index de955f0f15..2facaf53a6 100644 --- a/frontend/src/app/main/data/workspace/path/helpers.cljs +++ b/frontend/src/app/main/data/workspace/path/helpers.cljs @@ -12,7 +12,6 @@ [app.common.geom.shapes :as gsh] [app.common.math :as mth] [app.common.svg.path.command :as upc] - [app.common.svg.path.subpath :as ups] [app.main.data.workspace.path.common :as common] [app.util.mouse :as mse] [potok.v2.core :as ptk])) @@ -117,7 +116,6 @@ (let [command (next-node shape position prev-point prev-handler)] (-> shape (update :content (fnil conj []) command) - (update :content ups/close-subpaths) (update-selrect)))) (defn angle-points [common p1 p2] diff --git a/frontend/src/app/main/data/workspace/persistence.cljs b/frontend/src/app/main/data/workspace/persistence.cljs index b506c13bc5..8cfc5a87f4 100644 --- a/frontend/src/app/main/data/workspace/persistence.cljs +++ b/frontend/src/app/main/data/workspace/persistence.cljs @@ -234,7 +234,7 @@ (= (ptk/type event) ::changes-persisted)) (defn shapes-changes-persisted - [file-id {:keys [revn changes]}] + [file-id {:keys [revn changes] persisted-session-id :session-id}] (dm/assert! (uuid? file-id)) (dm/assert! (int? revn)) (dm/assert! (cpc/check-changes! changes)) @@ -245,24 +245,28 @@ ;; NOTE: we don't set the file features context here because ;; there are no useful context for code that need to be executed ;; on the frontend side - - (if-let [current-file-id (:current-file-id state)] - (if (= file-id current-file-id) - (let [changes (group-by :page-id changes)] + (let [current-file-id (:current-file-id state) + current-session-id (:session-id state)] + (if (and (some? current-file-id) + ;; If the remote change is from teh current session we skip + (not= persisted-session-id current-session-id)) + (if (= file-id current-file-id) + (let [changes (group-by :page-id changes)] + (-> state + (update-in [:workspace-file :revn] max revn) + (update :workspace-data + (fn [file] + (loop [fdata file + entries (seq changes)] + (if-let [[page-id changes] (first entries)] + (recur (-> fdata + (cpc/process-changes changes) + (cond-> (some? page-id) + (ctst/update-object-indices page-id))) + (rest entries)) + fdata)))))) (-> state - (update-in [:workspace-file :revn] max revn) - (update :workspace-data (fn [file] - (loop [fdata file - entries (seq changes)] - (if-let [[page-id changes] (first entries)] - (recur (-> fdata - (cpc/process-changes changes) - (cond-> (some? page-id) - (ctst/update-object-indices page-id))) - (rest entries)) - fdata)))))) - (-> state - (update-in [:workspace-libraries file-id :revn] max revn) - (update-in [:workspace-libraries file-id :data] cpc/process-changes changes))) + (update-in [:workspace-libraries file-id :revn] max revn) + (update-in [:workspace-libraries file-id :data] cpc/process-changes changes))) - state)))) + state))))) diff --git a/frontend/src/app/main/data/workspace/texts.cljs b/frontend/src/app/main/data/workspace/texts.cljs index 5f7bfb4997..c3e98739ba 100644 --- a/frontend/src/app/main/data/workspace/texts.cljs +++ b/frontend/src/app/main/data/workspace/texts.cljs @@ -52,6 +52,14 @@ (when-let [editor (:workspace-editor state)] (ts/schedule #(.focus ^js editor)))))) +(defn gen-name + [editor] + (when (some? editor) + (let [result + (-> (ted/get-editor-current-plain-text editor) + (txt/generate-shape-name))] + (when (not= result "") result)))) + (defn update-editor-state [{:keys [id] :as shape} editor-state] (ptk/reify ::update-editor-state @@ -62,7 +70,7 @@ (update state :workspace-editor-state dissoc id))))) (defn finalize-editor-state - [id] + [id update-name?] (ptk/reify ::finalize-editor-state ptk/WatchEvent (watch [_ state _] @@ -72,8 +80,8 @@ editor-state (get-in state [:workspace-editor-state id]) content (-> editor-state (ted/get-editor-current-content)) - text (-> (ted/get-editor-current-plain-text editor-state) - (txt/generate-shape-name)) + name (gen-name editor-state) + new-shape? (nil? (:content shape))] (if (ted/content-has-text? content) (let [content (d/merge (ted/export-content content) @@ -93,8 +101,8 @@ (assoc :content content) (cond-> position-data (assoc :position-data position-data)) - (cond-> new-shape? - (assoc :name text)) + (cond-> (and update-name? (some? name)) + (assoc :name name)) (cond-> (or (some? width) (some? height)) (gsh/transform-shape (ctm/change-size shape width height)))))) {:undo-group (when new-shape? id)}))))) @@ -104,29 +112,31 @@ (dwsh/delete-shapes #{id}))))))))) (defn initialize-editor-state - [{:keys [id content] :as shape} decorator] + [{:keys [id name content] :as shape} decorator] (ptk/reify ::initialize-editor-state ptk/UpdateEvent (update [_ state] - (let [text-state (some->> content ted/import-content) - attrs (d/merge txt/default-text-attrs - (get-in state [:workspace-global :default-font])) - editor (cond-> (ted/create-editor-state text-state decorator) - (and (nil? content) (some? attrs)) - (ted/update-editor-current-block-data attrs))] + (let [text-state (some->> content ted/import-content) + attrs (d/merge txt/default-text-attrs + (get-in state [:workspace-global :default-font])) + editor (cond-> (ted/create-editor-state text-state decorator) + (and (nil? content) (some? attrs)) + (ted/update-editor-current-block-data attrs))] (-> state (assoc-in [:workspace-editor-state id] editor)))) ptk/WatchEvent - (watch [_ _ stream] + (watch [_ state stream] ;; We need to finalize editor on two main events: (1) when user ;; explicitly navigates to other section or page; (2) when user ;; leaves the editor. - (->> (rx/merge - (rx/filter (ptk/type? ::rt/navigate) stream) - (rx/filter #(= ::finalize-editor-state %) stream)) - (rx/take 1) - (rx/map #(finalize-editor-state id)))))) + (let [editor (dm/get-in state [:workspace-editor-state id]) + update-name? (or (nil? content) (= name (gen-name editor)))] + (->> (rx/merge + (rx/filter (ptk/type? ::rt/navigate) stream) + (rx/filter #(= ::finalize-editor-state %) stream)) + (rx/take 1) + (rx/map #(finalize-editor-state id update-name?))))))) (defn select-all "Select all content of the current editor. When not editor found this diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs index 33502346b6..58fce57916 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -82,15 +82,27 @@ (dm/get-in state [:dashboard-local :selected-project])) st/state)) +(defn- dashboard-extract-selected + [files selected] + (let [get-file #(get files %) + sim-file #(select-keys % [:id :name :project-id :is-shared]) + xform (comp (map get-file) + (map sim-file))] + (->> (into #{} xform selected) + (d/index-by :id)))) + +(def dashboard-selected-search + (l/derived (fn [state] + ;; we need to this because :dashboard-search-result is a list + ;; of maps and we need a map of maps (using :id as key). + (let [files (d/index-by :id (:dashboard-search-result state))] + (dashboard-extract-selected files (dm/get-in state [:dashboard-local :selected-files])))) + st/state)) + (def dashboard-selected-files (l/derived (fn [state] - (let [get-file #(dm/get-in state [:dashboard-files %]) - sim-file #(select-keys % [:id :name :project-id :is-shared]) - selected (get-in state [:dashboard-local :selected-files]) - xform (comp (map get-file) - (map sim-file))] - (->> (into #{} xform selected) - (d/index-by :id)))) + (dashboard-extract-selected (:dashboard-files state) + (dm/get-in state [:dashboard-local :selected-files]))) st/state =)) ;; ---- Workspace refs diff --git a/frontend/src/app/main/render.cljs b/frontend/src/app/main/render.cljs index 861935a54c..0a19c8a84c 100644 --- a/frontend/src/app/main/render.cljs +++ b/frontend/src/app/main/render.cljs @@ -44,6 +44,7 @@ [app.main.ui.shapes.svg-raw :as svg-raw] [app.main.ui.shapes.text :as text] [app.main.ui.shapes.text.fontfaces :as ff] + [app.util.dom :as dom] [app.util.http :as http] [app.util.strings :as ust] [app.util.thumbnails :as th] @@ -626,7 +627,7 @@ bounds (gsb/get-object-bounds objects shape) background (when (str/ends-with? object-id "component") - (or (:background options) "#aab5ba")) + (or (:background options) (dom/get-css-variable "--assets-component-background-color") "#fff")) x (dm/get-prop bounds :x) y (dm/get-prop bounds :y) diff --git a/frontend/src/app/main/ui/components/color_bullet_new.cljs b/frontend/src/app/main/ui/components/color_bullet_new.cljs index f15f3549c8..690a0dd794 100644 --- a/frontend/src/app/main/ui/components/color_bullet_new.cljs +++ b/frontend/src/app/main/ui/components/color_bullet_new.cljs @@ -26,7 +26,9 @@ (^function on-click color event))))] (if (uc/multiple? color) - [:div {:on-click on-click :class (stl/css :color-bullet :multiple)}] + [:div {:class (stl/css :color-bullet :multiple) + :on-click on-click + :title (:color color)}] ;; No multiple selection (let [color (if (string? color) {:color color :opacity 1} color) id (:id color) @@ -44,7 +46,8 @@ :grid-area area :read-only read-only?) :data-readonly (str read-only?) - :on-click on-click} + :on-click on-click + :title (:color color)} (cond (some? gradient) @@ -72,6 +75,7 @@ :color-text (< size 72) :small-text (and (>= size 64) (< size 72)) :big-text (>= size 72)) + :title name :on-click on-click :on-double-click on-double-click} (if (some? image) diff --git a/frontend/src/app/main/ui/components/color_bullet_new.scss b/frontend/src/app/main/ui/components/color_bullet_new.scss index 9da0251159..d6e617251a 100644 --- a/frontend/src/app/main/ui/components/color_bullet_new.scss +++ b/frontend/src/app/main/ui/components/color_bullet_new.scss @@ -85,8 +85,9 @@ .big-text { @include inspectValue; + @include twoLineTextEllipsis; color: var(--palette-text-color); - height: $s-16; + height: $s-28; text-align: center; } diff --git a/frontend/src/app/main/ui/components/select.scss b/frontend/src/app/main/ui/components/select.scss index 6e47f82d18..1f6621c914 100644 --- a/frontend/src/app/main/ui/components/select.scss +++ b/frontend/src/app/main/ui/components/select.scss @@ -28,7 +28,6 @@ .current-icon { @include flexCenter; - height: $s-24; width: $s-24; padding-right: $s-4; svg { @@ -49,6 +48,7 @@ .separator { margin: 0; height: $s-12; + border-top: 1px solid $db-primary; } } .checked-element { diff --git a/frontend/src/app/main/ui/components/shape_icon_refactor.cljs b/frontend/src/app/main/ui/components/shape_icon_refactor.cljs index 3b856e929c..d4a9cf9274 100644 --- a/frontend/src/app/main/ui/components/shape_icon_refactor.cljs +++ b/frontend/src/app/main/ui/components/shape_icon_refactor.cljs @@ -47,7 +47,7 @@ :exclude i/boolean-exclude-refactor :intersection i/boolean-intersection-refactor #_:default i/boolean-union-refactor) - :svg-raw i/path-refactor + :svg-raw i/img-refactor nil))) diff --git a/frontend/src/app/main/ui/components/tab_container.cljs b/frontend/src/app/main/ui/components/tab_container.cljs index 9373027413..495ceeec21 100644 --- a/frontend/src/app/main/ui/components/tab_container.cljs +++ b/frontend/src/app/main/ui/components/tab_container.cljs @@ -19,7 +19,7 @@ (mf/defc tab-element {::mf/wrap-props false} [{:keys [children]}] - [:div {:class (stl/css :tab-element)} children]) + children) (mf/defc tab-container {::mf/wrap-props false} @@ -41,8 +41,8 @@ (when (fn? on-change-tab) (on-change-tab id)))))] - [:div {:class (stl/css :tab-container)} - [:div {:class (dm/str header-class " " (stl/css :tab-container-tabs))} + [:section {:class (stl/css :tab-container)} + [:header {:class (dm/str header-class " " (stl/css :tab-container-tabs))} (when ^boolean collapsable [:button {:on-click handle-collapse diff --git a/frontend/src/app/main/ui/components/tab_container.scss b/frontend/src/app/main/ui/components/tab_container.scss index 06ac807ebb..2db5c9df33 100644 --- a/frontend/src/app/main/ui/components/tab_container.scss +++ b/frontend/src/app/main/ui/components/tab_container.scss @@ -7,33 +7,21 @@ .tab-container { display: grid; - grid-template-rows: auto 1fr; - grid-template-columns: 100%; + grid-template-rows: $s-32 1fr; height: 100%; - - .tab-container-content { - overflow-y: auto; - overflow-x: hidden; - display: flex; - flex-direction: column; - } - - .tab-element { - flex: 1; - height: 100%; - } } + .tab-container-tabs { display: flex; align-items: center; flex-direction: row; gap: $s-2; - height: $s-32; border-radius: $br-8; background: var(--color-background-secondary); padding: $s-2; cursor: pointer; font-size: $fs-12; + height: 100%; .tab-container-tab-wrapper { @include flexCenter; flex-direction: row; @@ -72,6 +60,7 @@ } } } + .collapse-sidebar { @include flexCenter; @include buttonStyle; @@ -103,3 +92,10 @@ } } } + +.tab-container-content { + overflow-y: auto; + overflow-x: hidden; + display: flex; + flex-direction: column; +} diff --git a/frontend/src/app/main/ui/dashboard.cljs b/frontend/src/app/main/ui/dashboard.cljs index 36aa4b7efd..a568735159 100644 --- a/frontend/src/app/main/ui/dashboard.cljs +++ b/frontend/src/app/main/ui/dashboard.cljs @@ -30,6 +30,7 @@ [app.util.keyboard :as kbd] [app.util.object :as obj] [goog.events :as events] + [okulary.core :as l] [rumext.v2 :as mf])) (defn ^boolean uuid-str? @@ -81,6 +82,7 @@ (mf/use-effect on-resize) + [:div {:class (stl/css :dashboard-content) :on-click clear-selected-fn :ref container} (case section @@ -137,6 +139,9 @@ nil)])) +(def dashboard-initialized + (l/derived :current-team-id st/state)) + (mf/defc dashboard [{:keys [route profile] :as props}] (let [section (get-in route [:data :name]) @@ -150,7 +155,9 @@ team (get teams team-id) projects (mf/deref refs/dashboard-projects) - project (get projects project-id)] + project (get projects project-id) + + initialized? (mf/deref dashboard-initialized)] (hooks/use-shortcuts ::dashboard sc/shortcuts) @@ -177,7 +184,7 @@ ;; team-id because we want to completely refresh all the ;; components on team change. Many components assumes that the ;; team is already set so don't put the team into mf/deps. - (when team + (when (and team initialized?) [:main {:class (stl/css :dashboard) :key (:id team)} [:& sidebar diff --git a/frontend/src/app/main/ui/dashboard/file_menu.cljs b/frontend/src/app/main/ui/dashboard/file_menu.cljs index edda4f2f48..283460530b 100644 --- a/frontend/src/app/main/ui/dashboard/file_menu.cljs +++ b/frontend/src/app/main/ui/dashboard/file_menu.cljs @@ -75,6 +75,7 @@ other-teams (remove #(= (:id %) current-team-id) (vals @teams)) current-projects (remove #(= (:id %) (:project-id file)) (:projects current-team)) + on-new-tab (fn [_] (let [path-params {:project-id (:project-id file) diff --git a/frontend/src/app/main/ui/dashboard/grid.cljs b/frontend/src/app/main/ui/dashboard/grid.cljs index 4badcf1172..172f4f4ec8 100644 --- a/frontend/src/app/main/ui/dashboard/grid.cljs +++ b/frontend/src/app/main/ui/dashboard/grid.cljs @@ -215,31 +215,34 @@ (mf/defc grid-item {:wrap [mf/memo]} - [{:keys [file navigate? origin library-view?] :as props}] + [{:keys [file origin library-view?] :as props}] (let [file-id (:id file) - local (mf/use-state {:menu-open false - :menu-pos nil - :edition false}) - selected-files (mf/deref refs/dashboard-selected-files) + selected-files (if (= origin :search) + (mf/deref refs/dashboard-selected-search) + (mf/deref refs/dashboard-selected-files)) + dashboard-local (mf/deref refs/dashboard-local) + file-menu-open? (:menu-open dashboard-local) + + selected? (contains? selected-files file-id) + node-ref (mf/use-ref) menu-ref (mf/use-ref) - selected? (contains? selected-files file-id) - on-menu-close (mf/use-fn - #(swap! local assoc :menu-open false)) + (fn [_] + (st/emit! (dd/hide-file-menu)))) on-select - (fn [event] - (when (and (or (not selected?) (> (count selected-files) 1)) - (not (:menu-open @local))) - (dom/stop-propagation event) - (let [shift? (kbd/shift? event)] - (when-not shift? - (st/emit! (dd/clear-selected-files))) - (st/emit! (dd/toggle-file-select file))))) + (mf/use-fn + (fn [event] + (when (or (not selected?) (> (count selected-files) 1)) + (dom/stop-propagation event) + (let [shift? (kbd/shift? event)] + (when-not shift? + (st/emit! (dd/clear-selected-files))) + (st/emit! (dd/toggle-file-select file)))))) on-navigate (mf/use-fn @@ -254,6 +257,7 @@ (mf/use-fn (mf/deps selected-files) (fn [event] + (st/emit! (dd/hide-file-menu)) (let [offset (dom/get-offset-position (.-nativeEvent event)) select-current? (not (contains? selected-files (:id file))) @@ -283,6 +287,7 @@ (mf/use-fn (mf/deps file selected?) (fn [event] + (dom/stop-propagation event) (dom/prevent-default event) (when-not selected? (when-not (kbd/shift? event) @@ -297,9 +302,7 @@ x (:left points)] (gpt/point x y)) client-position)] - (swap! local assoc - :menu-open true - :menu-pos position)))) + (st/emit! (dd/show-file-menu-with-position file-id position))))) edit (mf/use-fn @@ -308,16 +311,14 @@ (let [name (str/trim name)] (when (not= name "") (st/emit! (dd/rename-file (assoc file :name name))))) - (swap! local assoc :edition false))) + (st/emit! (dd/stop-edit-file-name)))) on-edit (mf/use-fn (mf/deps file) (fn [event] (dom/stop-propagation event) - (swap! local assoc - :edition true - :menu-open false))) + (st/emit! (dd/start-edit-file-name file-id)))) handle-key-down (mf/use-callback @@ -331,10 +332,6 @@ (on-select event)) ;; TODO Fix this )))] - (mf/with-effect [selected? local] - (when (and (not selected?) (:menu-open @local)) - (swap! local assoc :menu-open false))) - [:li {:class (stl/css-case :grid-item true :project-th true :library library-view?)} [:button @@ -363,13 +360,13 @@ [:div {:class (stl/css :info-wrapper)} [:div {:class (stl/css :item-info)} - (if (:edition @local) + (if (and (= file-id (:file-id dashboard-local)) (:edition dashboard-local)) [:& inline-edition {:content (:name file) :on-end edit}] [:h3 (:name file)]) [:& grid-item-metadata {:modified-at (:modified-at file)}]] - [:div {:class (stl/css-case :project-th-actions true :force-display (:menu-open @local))} + [:div {:class (stl/css-case :project-th-actions true :force-display (:menu-open dashboard-local))} [:div {:class (stl/css :project-th-icon :menu) :tab-index "0" @@ -381,16 +378,15 @@ (dom/stop-propagation event) (on-menu-click event)))} i/actions - (when selected? + (when (and selected? file-menu-open?) [:& file-menu {:files (vals selected-files) - :show? (:menu-open @local) - :left (+ 24 (:x (:menu-pos @local))) - :top (:y (:menu-pos @local)) - :navigate? navigate? + :show? (:menu-open dashboard-local) + :left (+ 24 (:x (:menu-pos dashboard-local))) + :top (:y (:menu-pos dashboard-local)) + :navigate? true :on-edit on-edit :on-menu-close on-menu-close :origin origin - :dashboard-local dashboard-local :parent-id (str file-id "-action-menu")}])]]]]])) (mf/defc grid diff --git a/frontend/src/app/main/ui/dashboard/team.cljs b/frontend/src/app/main/ui/dashboard/team.cljs index 50a83cb308..1cb240817c 100644 --- a/frontend/src/app/main/ui/dashboard/team.cljs +++ b/frontend/src/app/main/ui/dashboard/team.cljs @@ -692,8 +692,11 @@ [:& header {:section :dashboard-team-invitations :team team}] [:section {:class (stl/css :dashboard-container :dashboard-team-invitations)} - [:& invitation-section {:team team - :invitations invitations}]]])) + ;; TODO: We should consider adding a "loading state" here + ;; with an (if (nil? invitations) [:& loading-state] [:& invitations]) + (when-not (nil? invitations) + [:& invitation-section {:team team + :invitations invitations}])]])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; WEBHOOKS SECTION diff --git a/frontend/src/app/main/ui/flex_controls.cljs b/frontend/src/app/main/ui/flex_controls.cljs new file mode 100644 index 0000000000..a21c6b9d90 --- /dev/null +++ b/frontend/src/app/main/ui/flex_controls.cljs @@ -0,0 +1,16 @@ +;; 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) KALEIDOS INC + +(ns app.main.ui.flex-controls + (:require + [app.common.data.macros :as dm] + [app.main.ui.flex-controls.gap :as fcg] + [app.main.ui.flex-controls.margin :as fcm] + [app.main.ui.flex-controls.padding :as fcp])) + +(dm/export fcg/gap-control) +(dm/export fcm/margin-control) +(dm/export fcp/padding-control) diff --git a/frontend/src/app/main/ui/flex_controls/common.cljs b/frontend/src/app/main/ui/flex_controls/common.cljs new file mode 100644 index 0000000000..052e501ee2 --- /dev/null +++ b/frontend/src/app/main/ui/flex_controls/common.cljs @@ -0,0 +1,35 @@ +(ns app.main.ui.flex-controls.common + (:require + [app.main.ui.formats :as fmt] + [rumext.v2 :as mf])) + +;; ------------------------------------------------ +;; CONSTANTS +;; ------------------------------------------------ + +(def font-size 11) +(def distance-color "var(--color-distance)") +(def distance-text-color "var(--color-white)") +(def warning-color "var(--color-warning)") +(def flex-display-pill-width 40) +(def flex-display-pill-height 20) +(def flex-display-pill-border-radius 4) + +(mf/defc flex-display-pill + [{:keys [x y width height font-size border-radius value color]}] + [:g.distance-pill + [:rect {:x x + :y y + :width width + :height height + :rx border-radius + :ry border-radius + :style {:fill color}}] + + [:text {:x (+ x (/ width 2)) + :y (+ y (/ height 2)) + :text-anchor "middle" + :dominant-baseline "central" + :style {:fill distance-text-color + :font-size font-size}} + (fmt/format-number (or value 0))]]) diff --git a/frontend/src/app/main/ui/flex_controls/gap.cljs b/frontend/src/app/main/ui/flex_controls/gap.cljs new file mode 100644 index 0000000000..bd38eba107 --- /dev/null +++ b/frontend/src/app/main/ui/flex_controls/gap.cljs @@ -0,0 +1,311 @@ +;; 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) KALEIDOS INC + +(ns app.main.ui.flex-controls.gap + (:require + [app.common.data :as d] + [app.common.files.helpers :as cfh] + [app.common.geom.point :as gpt] + [app.common.geom.shapes :as gsh] + [app.common.geom.shapes.flex-layout :as gsl] + [app.common.geom.shapes.points :as gpo] + [app.common.types.modifiers :as ctm] + [app.common.types.shape.layout :as ctl] + [app.main.data.workspace.modifiers :as dwm] + [app.main.data.workspace.state-helpers :as wsh] + [app.main.refs :as refs] + [app.main.store :as st] + [app.main.ui.css-cursors :as cur] + [app.main.ui.flex-controls.common :as fcc] + [app.main.ui.workspace.viewport.viewport-ref :refer [point->viewport]] + [app.util.dom :as dom] + [rumext.v2 :as mf])) + +(mf/defc gap-display + [{:keys [frame-id zoom gap-type gap on-pointer-enter on-pointer-leave + rect-data hover? selected? mouse-pos hover-value + on-move-selected on-context-menu]}] + (let [resizing (mf/use-var nil) + start (mf/use-var nil) + original-value (mf/use-var 0) + negate? (:resize-negate? rect-data) + axis (:resize-axis rect-data) + + on-pointer-down + (mf/use-fn + (mf/deps frame-id gap-type gap) + (fn [event] + (dom/capture-pointer event) + (reset! resizing gap-type) + (reset! start (dom/get-client-position event)) + (reset! original-value (:initial-value rect-data)))) + + on-lost-pointer-capture + (mf/use-fn + (mf/deps frame-id gap-type gap) + (fn [event] + (dom/release-pointer event) + (reset! resizing nil) + (reset! start nil) + (reset! original-value 0) + (st/emit! (dwm/apply-modifiers)))) + + on-pointer-move + (mf/use-fn + (mf/deps frame-id gap-type gap) + (fn [event] + (let [pos (dom/get-client-position event)] + (reset! mouse-pos (point->viewport pos)) + (when (= @resizing gap-type) + (let [delta (-> (gpt/to-vec @start pos) + (cond-> negate? gpt/negate) + (get axis)) + val (int (max (+ @original-value (/ delta zoom)) 0)) + layout-gap (assoc gap gap-type val) + modifiers (dwm/create-modif-tree [frame-id] (ctm/change-property (ctm/empty) :layout-gap layout-gap))] + + (reset! hover-value val) + (st/emit! (dwm/set-modifiers modifiers)))))))] + + [:g.gap-rect + [:rect.info-area + {:x (:x rect-data) + :y (:y rect-data) + :width (:width rect-data) + :height (:height rect-data) + :on-pointer-enter on-pointer-enter + :on-pointer-leave on-pointer-leave + :on-pointer-move on-pointer-move + :on-pointer-down on-move-selected + :on-context-menu on-context-menu + + :style {:fill (if (or hover? selected?) fcc/distance-color "none") + :opacity (if selected? 0.5 0.25)}}] + + (let [handle-width + (if (= axis :x) + (/ 2 zoom) + (min (* (:width rect-data) 0.5) (/ 20 zoom))) + + handle-height + (if (= axis :y) + (/ 2 zoom) + (min (* (:height rect-data) 0.5) (/ 30 zoom)))] + [:rect.handle + {:x (+ (:x rect-data) (/ (- (:width rect-data) handle-width) 2)) + :y (+ (:y rect-data) (/ (- (:height rect-data) handle-height) 2)) + :width handle-width + :height handle-height + :on-pointer-enter on-pointer-enter + :on-pointer-leave on-pointer-leave + :on-pointer-down on-pointer-down + :on-lost-pointer-capture on-lost-pointer-capture + :on-pointer-move on-pointer-move + :on-context-menu on-context-menu + :class (when (or hover? selected?) + (if (= (:resize-axis rect-data) :x) (cur/get-dynamic "resize-ew" 0) (cur/get-dynamic "resize-ew" 90))) + :style {:fill (if (or hover? selected?) fcc/distance-color "none") + :opacity (if selected? 0 1)}}])])) + +(mf/defc gap-rects + [{:keys [frame zoom on-move-selected on-context-menu]}] + (let [frame-id (:id frame) + saved-dir (:layout-flex-dir frame) + is-col? (or (= :column saved-dir) (= :column-reverse saved-dir)) + flip-x (:flip-x frame) + flip-y (:flip-y frame) + pill-width (/ fcc/flex-display-pill-width zoom) + pill-height (/ fcc/flex-display-pill-height zoom) + workspace-modifiers (mf/deref refs/workspace-modifiers) + gap-selected (mf/deref refs/workspace-gap-selected) + hover (mf/use-var nil) + hover-value (mf/use-var 0) + mouse-pos (mf/use-var nil) + padding (:layout-padding frame) + gap (:layout-gap frame) + {:keys [width height x1 y1]} (:selrect frame) + on-pointer-enter (fn [hover-type val] + (reset! hover hover-type) + (reset! hover-value val)) + + on-pointer-leave #(reset! hover nil) + negate {:column-gap (if flip-x true false) + :row-gap (if flip-y true false)} + + objects (wsh/lookup-page-objects @st/state) + children (->> (cfh/get-immediate-children objects frame-id) + (remove ctl/position-absolute?)) + + children-to-display (if (or (= :row-reverse saved-dir) + (= :column-reverse saved-dir)) + (drop-last children) + (rest children)) + children-to-display (->> children-to-display + (map #(gsh/transform-shape % (get-in workspace-modifiers [(:id %) :modifiers])))) + + wrap-blocks + (let [block-children (->> children + (map #(vector (gpo/parent-coords-bounds (:points %) (:points frame)) %))) + bounds (d/lazy-map (keys objects) #(gsh/shape->points (get objects %))) + + layout-data (gsl/calc-layout-data frame (:points frame) block-children bounds objects) + layout-bounds (:layout-bounds layout-data) + xv #(gpo/start-hv layout-bounds %) + yv #(gpo/start-vv layout-bounds %)] + (for [{:keys [start-p line-width line-height layout-gap-row layout-gap-col num-children]} (:layout-lines layout-data)] + (let [line-width (if is-col? line-width (+ line-width (* (dec num-children) layout-gap-row))) + line-height (if is-col? (+ line-height (* (dec num-children) layout-gap-col)) line-height) + end-p (-> start-p (gpt/add (xv line-width)) (gpt/add (yv line-height)))] + {:x1 (min (:x start-p) (:x end-p)) + :y1 (min (:y start-p) (:y end-p)) + :x2 (max (:x start-p) (:x end-p)) + :y2 (max (:y start-p) (:y end-p))}))) + + block-contains + (fn [x y block] + (if is-col? + (<= (:x1 block) x (:x2 block)) + (<= (:y1 block) y (:y2 block)))) + + get-container-block + (fn [shape] + (let [selrect (:selrect shape) + x (/ (+ (:x1 selrect) (:x2 selrect)) 2) + y (/ (+ (:y1 selrect) (:y2 selrect)) 2)] + (->> wrap-blocks + (filter #(block-contains x y %)) + first))) + + create-cgdd + (fn [shape] + (let [block (get-container-block shape) + x (if flip-x + (- (:x1 (:selrect shape)) + (get-in shape [:layout-item-margin :m2]) + (:column-gap gap)) + (+ (:x2 (:selrect shape)) (get-in shape [:layout-item-margin :m2]))) + y (:y1 block) + h (- (:y2 block) (:y1 block))] + {:x x + :y y + :height h + :width (:column-gap gap) + :initial-value (:column-gap gap) + :resize-type :left + :resize-axis :x + :resize-negate? (:column-gap negate) + :gap-type (if is-col? :row-gap :column-gap)})) + + create-cgdd-block + (fn [block] + (let [x (if flip-x + (- (:x1 block) (:column-gap gap)) + (:x2 block)) + y (if flip-y + (+ y1 (:p3 padding)) + (+ y1 (:p1 padding))) + h (- height (+ (:p1 padding) (:p3 padding)))] + {:x x + :y y + :width (:column-gap gap) + :height h + :initial-value (:column-gap gap) + :resize-type :left + :resize-axis :x + :resize-negate? (:column-gap negate) + :gap-type (if is-col? :column-gap :row-gap)})) + + create-rgdd + (fn [shape] + (let [block (get-container-block shape) + x (:x1 block) + y (if flip-y + (- (:y1 (:selrect shape)) + (get-in shape [:layout-item-margin :m3]) + (:row-gap gap)) + (+ (:y2 (:selrect shape)) (get-in shape [:layout-item-margin :m3]))) + w (- (:x2 block) (:x1 block))] + {:x x + :y y + :width w + :height (:row-gap gap) + :initial-value (:row-gap gap) + :resize-type :bottom + :resize-axis :y + :resize-negate? (:row-gap negate) + :gap-type (if is-col? :row-gap :column-gap)})) + + create-rgdd-block + (fn [block] + (let [x (if flip-x + (+ x1 (:p2 padding)) + (+ x1 (:p4 padding))) + y (if flip-y + (- (:y1 block) (:row-gap gap)) + (:y2 block)) + w (- width (+ (:p2 padding) (:p4 padding)))] + {:x x + :y y + :width w + :height (:row-gap gap) + :initial-value (:row-gap gap) + :resize-type :bottom + :resize-axis :y + :resize-negate? (:row-gap negate) + :gap-type (if is-col? :column-gap :row-gap)})) + + display-blocks (if is-col? + (->> (drop-last wrap-blocks) + (map create-cgdd-block)) + (->> (drop-last wrap-blocks) + (map create-rgdd-block))) + + display-children (if is-col? + (->> children-to-display + (map create-rgdd)) + (->> children-to-display + (map create-cgdd)))] + + [:g.gaps {:pointer-events "visible"} + (for [[index display-item] (d/enumerate (concat display-blocks display-children))] + (let [gap-type (:gap-type display-item)] + [:& gap-display {:key (str frame-id index) + :frame-id frame-id + :zoom zoom + :gap-type gap-type + :gap gap + :on-pointer-enter (partial on-pointer-enter gap-type (get gap gap-type)) + :on-pointer-leave on-pointer-leave + :on-move-selected on-move-selected + :on-context-menu on-context-menu + :rect-data display-item + :hover? (= @hover gap-type) + :selected? (= gap-selected gap-type) + :mouse-pos mouse-pos + :hover-value hover-value}])) + + (when @hover + [:& fcc/flex-display-pill + {:height pill-height + :width pill-width + :font-size (/ fcc/font-size zoom) + :border-radius (/ fcc/flex-display-pill-border-radius zoom) + :color fcc/distance-color + :x (:x @mouse-pos) + :y (- (:y @mouse-pos) pill-width) + :value @hover-value}])])) + + +(mf/defc gap-control + [{:keys [frame zoom on-move-selected on-context-menu]}] + (when frame + [:g.measurement-gaps {:pointer-events "none"} + [:g.hover-shapes + [:& gap-rects + {:frame frame + :zoom zoom + :on-move-selected on-move-selected + :on-context-menu on-context-menu}]]])) diff --git a/frontend/src/app/main/ui/flex_controls/margin.cljs b/frontend/src/app/main/ui/flex_controls/margin.cljs new file mode 100644 index 0000000000..5ee7d33c55 --- /dev/null +++ b/frontend/src/app/main/ui/flex_controls/margin.cljs @@ -0,0 +1,185 @@ +;; 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) KALEIDOS INC + +(ns app.main.ui.flex-controls.margin + (:require + [app.common.geom.point :as gpt] + [app.common.types.modifiers :as ctm] + [app.main.data.workspace.modifiers :as dwm] + [app.main.refs :as refs] + [app.main.store :as st] + [app.main.ui.css-cursors :as cur] + [app.main.ui.flex-controls.common :as fcc] + [app.main.ui.workspace.viewport.viewport-ref :refer [point->viewport]] + [app.util.dom :as dom] + [rumext.v2 :as mf])) + +(mf/defc margin-display [{:keys [shape-id zoom hover-all? hover-v? hover-h? margin-num margin on-pointer-enter on-pointer-leave + rect-data hover? selected? mouse-pos hover-value]}] + (let [resizing? (mf/use-var false) + start (mf/use-var nil) + original-value (mf/use-var 0) + negate? (true? (:resize-negate? rect-data)) + axis (:resize-axis rect-data) + + on-pointer-down + (mf/use-fn + (mf/deps shape-id margin-num margin) + (fn [event] + (dom/capture-pointer event) + (reset! resizing? true) + (reset! start (dom/get-client-position event)) + (reset! original-value (:initial-value rect-data)))) + + on-lost-pointer-capture + (mf/use-fn + (mf/deps shape-id margin-num margin) + (fn [event] + (dom/release-pointer event) + (reset! resizing? false) + (reset! start nil) + (reset! original-value 0) + (st/emit! (dwm/apply-modifiers)))) + + on-pointer-move + (mf/use-fn + (mf/deps shape-id margin-num margin hover-all? hover-v? hover-h?) + (fn [event] + (let [pos (dom/get-client-position event)] + (reset! mouse-pos (point->viewport pos)) + (when @resizing? + (let [delta (-> (gpt/to-vec @start pos) + (cond-> negate? gpt/negate) + (get axis)) + val (int (max (+ @original-value (/ delta zoom)) 0)) + layout-item-margin (cond + hover-all? (assoc margin :m1 val :m2 val :m3 val :m4 val) + hover-v? (assoc margin :m1 val :m3 val) + hover-h? (assoc margin :m2 val :m4 val) + :else (assoc margin margin-num val)) + layout-item-margin-type (if (= (:m1 margin) (:m2 margin) (:m3 margin) (:m4 margin)) :simple :multiple) + modifiers (dwm/create-modif-tree [shape-id] + (-> (ctm/empty) + (ctm/change-property :layout-item-margin layout-item-margin) + (ctm/change-property :layout-item-margin-type layout-item-margin-type)))] + (reset! hover-value val) + (st/emit! (dwm/set-modifiers modifiers)))))))] + + [:rect.margin-rect + {:x (:x rect-data) + :y (:y rect-data) + :width (:width rect-data) + :height (:height rect-data) + :on-pointer-enter on-pointer-enter + :on-pointer-leave on-pointer-leave + :on-pointer-down on-pointer-down + :on-lost-pointer-capture on-lost-pointer-capture + :on-pointer-move on-pointer-move + :class (when (or hover? selected?) + (if (= (:resize-axis rect-data) :x) (cur/get-dynamic "resize-ew" 0) (cur/get-dynamic "resize-ew" 90))) + :style {:fill (if (or hover? selected?) fcc/warning-color "none") + :opacity (if selected? 0.5 0.25)}}])) + + +(mf/defc margin-rects [{:keys [shape frame zoom alt? shift?]}] + (let [shape-id (:id shape) + pill-width (/ fcc/flex-display-pill-width zoom) + pill-height (/ fcc/flex-display-pill-height zoom) + margins-selected (mf/deref refs/workspace-margins-selected) + hover-value (mf/use-var 0) + mouse-pos (mf/use-var nil) + hover (mf/use-var nil) + hover-all? (and (not (nil? @hover)) alt?) + hover-v? (and (or (= @hover :m1) (= @hover :m3)) shift?) + hover-h? (and (or (= @hover :m2) (= @hover :m4)) shift?) + margin (:layout-item-margin shape) + {:keys [width height x1 x2 y1 y2]} (:selrect shape) + on-pointer-enter (fn [hover-type val] + (reset! hover hover-type) + (reset! hover-value val)) + on-pointer-leave #(reset! hover nil) + hover? #(or hover-all? + (and (or (= % :m1) (= % :m3)) hover-v?) + (and (or (= % :m2) (= % :m4)) hover-h?) + (= @hover %)) + margin-display-data {:m1 {:key (str shape-id "-m1") + :x x1 + :y (if (:flip-y frame) y2 (- y1 (:m1 margin))) + :width width + :height (:m1 margin) + :initial-value (:m1 margin) + :resize-type :top + :resize-axis :y + :resize-negate? (:flip-y frame)} + :m2 {:key (str shape-id "-m2") + :x (if (:flip-x frame) (- x1 (:m2 margin)) x2) + :y y1 + :width (:m2 margin) + :height height + :initial-value (:m2 margin) + :resize-type :left + :resize-axis :x + :resize-negate? (:flip-x frame)} + :m3 {:key (str shape-id "-m3") + :x x1 + :y (if (:flip-y frame) (- y1 (:m3 margin)) y2) + :width width + :height (:m3 margin) + :initial-value (:m3 margin) + :resize-type :top + :resize-axis :y + :resize-negate? (:flip-y frame)} + :m4 {:key (str shape-id "-m4") + :x (if (:flip-x frame) x2 (- x1 (:m4 margin))) + :y y1 + :width (:m4 margin) + :height height + :initial-value (:m4 margin) + :resize-type :left + :resize-axis :x + :resize-negate? (:flip-x frame)}}] + + [:g.margins {:pointer-events "visible"} + (for [[margin-num rect-data] margin-display-data] + [:& margin-display + {:key (:key rect-data) + :shape-id shape-id + :zoom zoom + :hover-all? hover-all? + :hover-v? hover-v? + :hover-h? hover-h? + :margin-num margin-num + :margin margin + :on-pointer-enter (partial on-pointer-enter margin-num (get margin margin-num)) + :on-pointer-leave on-pointer-leave + :rect-data rect-data + :hover? (hover? margin-num) + :selected? (get margins-selected margin-num) + :mouse-pos mouse-pos + :hover-value hover-value}]) + + (when @hover + [:& fcc/flex-display-pill + {:height pill-height + :width pill-width + :font-size (/ fcc/font-size zoom) + :border-radius (/ fcc/flex-display-pill-border-radius zoom) + :color fcc/warning-color + :x (:x @mouse-pos) + :y (- (:y @mouse-pos) pill-width) + :value @hover-value}])])) + +(mf/defc margin-control + [{:keys [shape parent zoom alt? shift?]}] + (when shape + [:g.measurement-gaps {:pointer-events "none"} + [:g.hover-shapes + [:& margin-rects + {:shape shape + :frame parent + :zoom zoom + :alt? alt? + :shift? shift?}]]])) diff --git a/frontend/src/app/main/ui/flex_controls/padding.cljs b/frontend/src/app/main/ui/flex_controls/padding.cljs new file mode 100644 index 0000000000..5b64c94fb3 --- /dev/null +++ b/frontend/src/app/main/ui/flex_controls/padding.cljs @@ -0,0 +1,223 @@ +;; 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) KALEIDOS INC + +(ns app.main.ui.flex-controls.padding + (:require + [app.common.geom.point :as gpt] + [app.common.types.modifiers :as ctm] + [app.main.data.workspace.modifiers :as dwm] + [app.main.refs :as refs] + [app.main.store :as st] + [app.main.ui.css-cursors :as cur] + [app.main.ui.flex-controls.common :as fcc] + [app.main.ui.workspace.viewport.viewport-ref :refer [point->viewport]] + [app.util.dom :as dom] + [rumext.v2 :as mf])) + +(mf/defc padding-display + [{:keys [frame-id zoom hover-all? hover-v? hover-h? padding-num padding on-pointer-enter on-pointer-leave + rect-data hover? selected? mouse-pos hover-value on-move-selected on-context-menu]}] + (let [resizing? (mf/use-var false) + start (mf/use-var nil) + original-value (mf/use-var 0) + negate? (true? (:resize-negate? rect-data)) + axis (:resize-axis rect-data) + + on-pointer-down + (mf/use-fn + (mf/deps frame-id rect-data padding-num) + (fn [event] + (dom/capture-pointer event) + (reset! resizing? true) + (reset! start (dom/get-client-position event)) + (reset! original-value (:initial-value rect-data)))) + + on-lost-pointer-capture + (mf/use-fn + (mf/deps frame-id padding-num padding) + (fn [event] + (dom/release-pointer event) + (reset! resizing? false) + (reset! start nil) + (reset! original-value 0) + (st/emit! (dwm/apply-modifiers)))) + + on-pointer-move + (mf/use-fn + (mf/deps frame-id padding-num padding hover-all? hover-v? hover-h?) + (fn [event] + (let [pos (dom/get-client-position event)] + (reset! mouse-pos (point->viewport pos)) + (when @resizing? + (let [delta (-> (gpt/to-vec @start pos) + (cond-> negate? gpt/negate) + (get axis)) + val (int (max (+ @original-value (/ delta zoom)) 0)) + layout-padding (cond + hover-all? (assoc padding :p1 val :p2 val :p3 val :p4 val) + hover-v? (assoc padding :p1 val :p3 val) + hover-h? (assoc padding :p2 val :p4 val) + :else (assoc padding padding-num val)) + + + layout-padding-type (if (= (:p1 padding) (:p2 padding) (:p3 padding) (:p4 padding)) :simple :multiple) + modifiers (dwm/create-modif-tree [frame-id] + (-> (ctm/empty) + (ctm/change-property :layout-padding layout-padding) + (ctm/change-property :layout-padding-type layout-padding-type)))] + (reset! hover-value val) + (st/emit! (dwm/set-modifiers modifiers)))))))] + + [:g.padding-rect + [:rect.info-area + {:x (:x rect-data) + :y (:y rect-data) + :width (max 0 (:width rect-data)) + :height (max 0 (:height rect-data)) + + :on-pointer-enter on-pointer-enter + :on-pointer-leave on-pointer-leave + :on-pointer-move on-pointer-move + :on-pointer-down on-move-selected + :on-context-menu on-context-menu + :style {:fill (if (or hover? selected?) fcc/distance-color "none") + :opacity (if selected? 0.5 0.25)}}] + + (let [handle-width + (if (= axis :x) + (/ 2 zoom) + (min (* (:width rect-data) 0.5) (/ 20 zoom))) + + handle-height + (if (= axis :y) + (/ 2 zoom) + (min (* (:height rect-data) 0.5) (/ 30 zoom)))] + [:rect.handle + {:x (+ (:x rect-data) (/ (- (:width rect-data) handle-width) 2)) + :y (+ (:y rect-data) (/ (- (:height rect-data) handle-height) 2)) + :width handle-width + :height handle-height + :on-pointer-enter on-pointer-enter + :on-pointer-leave on-pointer-leave + :on-pointer-down on-pointer-down + :on-lost-pointer-capture on-lost-pointer-capture + :on-pointer-move on-pointer-move + :on-context-menu on-context-menu + :class (when (or hover? selected?) + (if (= (:resize-axis rect-data) :x) (cur/get-dynamic "resize-ew" 0) (cur/get-dynamic "resize-ew" 90))) + :style {:fill (if (or hover? selected?) fcc/distance-color "none") + :opacity (if selected? 0 1)}}])])) + +(mf/defc padding-rects + [{:keys [frame zoom alt? shift? on-move-selected on-context-menu]}] + (let [frame-id (:id frame) + paddings-selected (mf/deref refs/workspace-paddings-selected) + hover-value (mf/use-var 0) + mouse-pos (mf/use-var nil) + hover (mf/use-var nil) + hover-all? (and (not (nil? @hover)) alt?) + hover-v? (and (or (= @hover :p1) (= @hover :p3)) shift?) + hover-h? (and (or (= @hover :p2) (= @hover :p4)) shift?) + padding (:layout-padding frame) + {:keys [width height x1 x2 y1 y2]} (:selrect frame) + on-pointer-enter (fn [hover-type val] + (reset! hover hover-type) + (reset! hover-value val)) + on-pointer-leave #(reset! hover nil) + pill-width (/ fcc/flex-display-pill-width zoom) + pill-height (/ fcc/flex-display-pill-height zoom) + hover? #(or hover-all? + (and (or (= % :p1) (= % :p3)) hover-v?) + (and (or (= % :p2) (= % :p4)) hover-h?) + (= @hover %)) + negate {:p1 (if (:flip-y frame) true false) + :p2 (if (:flip-x frame) true false) + :p3 (if (:flip-y frame) true false) + :p4 (if (:flip-x frame) true false)} + negate (cond-> negate + (not= :auto (:layout-item-h-sizing frame)) (assoc :p2 (not (:p2 negate))) + (not= :auto (:layout-item-v-sizing frame)) (assoc :p3 (not (:p3 negate)))) + + padding-rect-data {:p1 {:key (str frame-id "-p1") + :x x1 + :y (if (:flip-y frame) (- y2 (:p1 padding)) y1) + :width width + :height (:p1 padding) + :initial-value (:p1 padding) + :resize-type (if (:flip-y frame) :bottom :top) + :resize-axis :y + :resize-negate? (:p1 negate)} + :p2 {:key (str frame-id "-p2") + :x (if (:flip-x frame) x1 (- x2 (:p2 padding))) + :y y1 + :width (:p2 padding) + :height height + :initial-value (:p2 padding) + :resize-type :left + :resize-axis :x + :resize-negate? (:p2 negate)} + :p3 {:key (str frame-id "-p3") + :x x1 + :y (if (:flip-y frame) y1 (- y2 (:p3 padding))) + :width width + :height (:p3 padding) + :initial-value (:p3 padding) + :resize-type :bottom + :resize-axis :y + :resize-negate? (:p3 negate)} + :p4 {:key (str frame-id "-p4") + :x (if (:flip-x frame) (- x2 (:p4 padding)) x1) + :y y1 + :width (:p4 padding) + :height height + :initial-value (:p4 padding) + :resize-type (if (:flip-x frame) :right :left) + :resize-axis :x + :resize-negate? (:p4 negate)}}] + + [:g.paddings {:pointer-events "visible"} + (for [[padding-num rect-data] padding-rect-data] + [:& padding-display + {:key (:key rect-data) + :frame-id frame-id + :zoom zoom + :hover-all? hover-all? + :hover-v? hover-v? + :hover-h? hover-h? + :padding padding + :mouse-pos mouse-pos + :hover-value hover-value + :padding-num padding-num + :on-pointer-enter (partial on-pointer-enter padding-num (get padding padding-num)) + :on-pointer-leave on-pointer-leave + :on-move-selected on-move-selected + :on-context-menu on-context-menu + :hover? (hover? padding-num) + :selected? (get paddings-selected padding-num) + :rect-data rect-data}]) + (when @hover + [:& fcc/flex-display-pill + {:height pill-height + :width pill-width + :font-size (/ fcc/font-size zoom) + :border-radius (/ fcc/flex-display-pill-border-radius zoom) + :color fcc/distance-color + :x (:x @mouse-pos) + :y (- (:y @mouse-pos) pill-width) + :value @hover-value}])])) + +(mf/defc padding-control + [{:keys [frame zoom alt? shift? on-move-selected on-context-menu]}] + (when frame + [:g.measurement-gaps {:pointer-events "none"} + [:g.hover-shapes + [:& padding-rects + {:frame frame + :zoom zoom + :alt? alt? + :shift? shift? + :on-move-selected on-move-selected + :on-context-menu on-context-menu}]]])) diff --git a/frontend/src/app/main/ui/icons.cljs b/frontend/src/app/main/ui/icons.cljs index f73fef49be..fd0c0657ff 100644 --- a/frontend/src/app/main/ui/icons.cljs +++ b/frontend/src/app/main/ui/icons.cljs @@ -466,6 +466,15 @@ (def ^:icon view-as-icons-refactor (icon-xref :view-as-icons-refactor)) (def ^:icon wrap-refactor (icon-xref :wrap-refactor)) (def ^:icon view-as-list-refactor (icon-xref :view-as-list-refactor)) +(def ^:icon cap-line-arrow (icon-xref :cap-line-arrow)) +(def ^:icon cap-triangle-arrow (icon-xref :cap-triangle-arrow)) +(def ^:icon cap-square-marker (icon-xref :cap-square-marker)) +(def ^:icon cap-circle-marker (icon-xref :cap-circle-marker)) +(def ^:icon cap-diamond-marker (icon-xref :cap-diamond-marker)) +(def ^:icon cap-round (icon-xref :cap-round)) +(def ^:icon cap-square (icon-xref :cap-square)) + + (def ^:icon loader-pencil (mf/html [:svg diff --git a/frontend/src/app/main/ui/measurements.cljs b/frontend/src/app/main/ui/measurements.cljs index 057794e307..9c4f9719c9 100644 --- a/frontend/src/app/main/ui/measurements.cljs +++ b/frontend/src/app/main/ui/measurements.cljs @@ -8,24 +8,12 @@ (:require [app.common.data :as d] [app.common.data.macros :as dm] - [app.common.files.helpers :as cfh] [app.common.geom.point :as gpt] [app.common.geom.rect :as grc] [app.common.geom.shapes :as gsh] - [app.common.geom.shapes.flex-layout :as gsl] - [app.common.geom.shapes.points :as gpo] [app.common.math :as mth] - [app.common.types.modifiers :as ctm] - [app.common.types.shape.layout :as ctl] [app.common.uuid :as uuid] - [app.main.data.workspace.modifiers :as dwm] - [app.main.data.workspace.state-helpers :as wsh] - [app.main.refs :as refs] - [app.main.store :as st] - [app.main.ui.css-cursors :as cur] [app.main.ui.formats :as fmt] - [app.main.ui.workspace.viewport.viewport-ref :refer [point->viewport]] - [app.util.dom :as dom] [rumext.v2 :as mf])) ;; ------------------------------------------------ @@ -57,9 +45,6 @@ (def distance-pill-height 16) (def distance-line-stroke 1) (def warning-color "var(--color-warning)") -(def flex-display-pill-width 40) -(def flex-display-pill-height 20) -(def flex-display-pill-border-radius 4) ;; ------------------------------------------------ ;; HELPERS @@ -277,600 +262,3 @@ [:& size-display {:selrect hover-selrect :zoom zoom}] [:& distance-display {:from hover-selrect :to selected-selrect :zoom zoom :bounds bounds-selrect}]])]))) - - -(mf/defc flex-display-pill [{:keys [x y width height font-size border-radius value color]}] - [:g.distance-pill - [:rect {:x x - :y y - :width width - :height height - :rx border-radius - :ry border-radius - :style {:fill color}}] - - [:text {:x (+ x (/ width 2)) - :y (+ y (/ height 2)) - :text-anchor "middle" - :dominant-baseline "central" - :style {:fill distance-text-color - :font-size font-size}} - (fmt/format-number (or value 0))]]) - - -(mf/defc padding-display [{:keys [frame-id zoom hover-all? hover-v? hover-h? padding-num padding on-pointer-enter on-pointer-leave - rect-data hover? selected? mouse-pos hover-value]}] - (let [resizing? (mf/use-var false) - start (mf/use-var nil) - original-value (mf/use-var 0) - negate? (true? (:resize-negate? rect-data)) - axis (:resize-axis rect-data) - - on-pointer-down - (mf/use-fn - (mf/deps frame-id rect-data padding-num) - (fn [event] - (dom/capture-pointer event) - (reset! resizing? true) - (reset! start (dom/get-client-position event)) - (reset! original-value (:initial-value rect-data)))) - - on-lost-pointer-capture - (mf/use-fn - (mf/deps frame-id padding-num padding) - (fn [event] - (dom/release-pointer event) - (reset! resizing? false) - (reset! start nil) - (reset! original-value 0) - (st/emit! (dwm/apply-modifiers)))) - - on-pointer-move - (mf/use-fn - (mf/deps frame-id padding-num padding hover-all? hover-v? hover-h?) - (fn [event] - (let [pos (dom/get-client-position event)] - (reset! mouse-pos (point->viewport pos)) - (when @resizing? - (let [delta (-> (gpt/to-vec @start pos) - (cond-> negate? gpt/negate) - (get axis)) - val (int (max (+ @original-value (/ delta zoom)) 0)) - layout-padding (cond - hover-all? (assoc padding :p1 val :p2 val :p3 val :p4 val) - hover-v? (assoc padding :p1 val :p3 val) - hover-h? (assoc padding :p2 val :p4 val) - :else (assoc padding padding-num val)) - - - layout-padding-type (if (= (:p1 padding) (:p2 padding) (:p3 padding) (:p4 padding)) :simple :multiple) - modifiers (dwm/create-modif-tree [frame-id] - (-> (ctm/empty) - (ctm/change-property :layout-padding layout-padding) - (ctm/change-property :layout-padding-type layout-padding-type)))] - (reset! hover-value val) - (st/emit! (dwm/set-modifiers modifiers)))))))] - - [:rect.padding-rect {:x (:x rect-data) - :y (:y rect-data) - :width (max 0 (:width rect-data)) - :height (max 0 (:height rect-data)) - :on-pointer-enter on-pointer-enter - :on-pointer-leave on-pointer-leave - :on-pointer-down on-pointer-down - :on-lost-pointer-capture on-lost-pointer-capture - :on-pointer-move on-pointer-move - :class (when (or hover? selected?) - (if (= (:resize-axis rect-data) :x) (cur/get-dynamic "resize-ew" 0) (cur/get-dynamic "resize-ew" 90))) - :style {:fill (if (or hover? selected?) distance-color "none") - :opacity (if selected? 0.5 0.25)}}])) - -(mf/defc padding-rects [{:keys [frame zoom alt? shift?]}] - (let [frame-id (:id frame) - paddings-selected (mf/deref refs/workspace-paddings-selected) - hover-value (mf/use-var 0) - mouse-pos (mf/use-var nil) - hover (mf/use-var nil) - hover-all? (and (not (nil? @hover)) alt?) - hover-v? (and (or (= @hover :p1) (= @hover :p3)) shift?) - hover-h? (and (or (= @hover :p2) (= @hover :p4)) shift?) - padding (:layout-padding frame) - {:keys [width height x1 x2 y1 y2]} (:selrect frame) - on-pointer-enter (fn [hover-type val] - (reset! hover hover-type) - (reset! hover-value val)) - on-pointer-leave #(reset! hover nil) - pill-width (/ flex-display-pill-width zoom) - pill-height (/ flex-display-pill-height zoom) - hover? #(or hover-all? - (and (or (= % :p1) (= % :p3)) hover-v?) - (and (or (= % :p2) (= % :p4)) hover-h?) - (= @hover %)) - negate {:p1 (if (:flip-y frame) true false) - :p2 (if (:flip-x frame) true false) - :p3 (if (:flip-y frame) true false) - :p4 (if (:flip-x frame) true false)} - negate (cond-> negate - (not= :auto (:layout-item-h-sizing frame)) (assoc :p2 (not (:p2 negate))) - (not= :auto (:layout-item-v-sizing frame)) (assoc :p3 (not (:p3 negate)))) - - padding-rect-data {:p1 {:key (str frame-id "-p1") - :x x1 - :y (if (:flip-y frame) (- y2 (:p1 padding)) y1) - :width width - :height (:p1 padding) - :initial-value (:p1 padding) - :resize-type (if (:flip-y frame) :bottom :top) - :resize-axis :y - :resize-negate? (:p1 negate)} - :p2 {:key (str frame-id "-p2") - :x (if (:flip-x frame) x1 (- x2 (:p2 padding))) - :y y1 - :width (:p2 padding) - :height height - :initial-value (:p2 padding) - :resize-type :left - :resize-axis :x - :resize-negate? (:p2 negate)} - :p3 {:key (str frame-id "-p3") - :x x1 - :y (if (:flip-y frame) y1 (- y2 (:p3 padding))) - :width width - :height (:p3 padding) - :initial-value (:p3 padding) - :resize-type :bottom - :resize-axis :y - :resize-negate? (:p3 negate)} - :p4 {:key (str frame-id "-p4") - :x (if (:flip-x frame) (- x2 (:p4 padding)) x1) - :y y1 - :width (:p4 padding) - :height height - :initial-value (:p4 padding) - :resize-type (if (:flip-x frame) :right :left) - :resize-axis :x - :resize-negate? (:p4 negate)}}] - - [:g.paddings {:pointer-events "visible"} - (for [[padding-num rect-data] padding-rect-data] - [:& padding-display {:key (:key rect-data) - :frame-id frame-id - :zoom zoom - :hover-all? hover-all? - :hover-v? hover-v? - :hover-h? hover-h? - :padding padding - :mouse-pos mouse-pos - :hover-value hover-value - :padding-num padding-num - :on-pointer-enter (partial on-pointer-enter padding-num (get padding padding-num)) - :on-pointer-leave on-pointer-leave - :hover? (hover? padding-num) - :selected? (get paddings-selected padding-num) - :rect-data rect-data}]) - (when @hover - [:& flex-display-pill {:height pill-height - :width pill-width - :font-size (/ font-size zoom) - :border-radius (/ flex-display-pill-border-radius zoom) - :color distance-color - :x (:x @mouse-pos) - :y (- (:y @mouse-pos) pill-width) - :value @hover-value}])])) - -(mf/defc margin-display [{:keys [shape-id zoom hover-all? hover-v? hover-h? margin-num margin on-pointer-enter on-pointer-leave - rect-data hover? selected? mouse-pos hover-value]}] - (let [resizing? (mf/use-var false) - start (mf/use-var nil) - original-value (mf/use-var 0) - negate? (true? (:resize-negate? rect-data)) - axis (:resize-axis rect-data) - - on-pointer-down - (mf/use-fn - (mf/deps shape-id margin-num margin) - (fn [event] - (dom/capture-pointer event) - (reset! resizing? true) - (reset! start (dom/get-client-position event)) - (reset! original-value (:initial-value rect-data)))) - - on-lost-pointer-capture - (mf/use-fn - (mf/deps shape-id margin-num margin) - (fn [event] - (dom/release-pointer event) - (reset! resizing? false) - (reset! start nil) - (reset! original-value 0) - (st/emit! (dwm/apply-modifiers)))) - - on-pointer-move - (mf/use-fn - (mf/deps shape-id margin-num margin hover-all? hover-v? hover-h?) - (fn [event] - (let [pos (dom/get-client-position event)] - (reset! mouse-pos (point->viewport pos)) - (when @resizing? - (let [delta (-> (gpt/to-vec @start pos) - (cond-> negate? gpt/negate) - (get axis)) - val (int (max (+ @original-value (/ delta zoom)) 0)) - layout-item-margin (cond - hover-all? (assoc margin :m1 val :m2 val :m3 val :m4 val) - hover-v? (assoc margin :m1 val :m3 val) - hover-h? (assoc margin :m2 val :m4 val) - :else (assoc margin margin-num val)) - layout-item-margin-type (if (= (:m1 margin) (:m2 margin) (:m3 margin) (:m4 margin)) :simple :multiple) - modifiers (dwm/create-modif-tree [shape-id] - (-> (ctm/empty) - (ctm/change-property :layout-item-margin layout-item-margin) - (ctm/change-property :layout-item-margin-type layout-item-margin-type)))] - (reset! hover-value val) - (st/emit! (dwm/set-modifiers modifiers)))))))] - - [:rect.margin-rect {:x (:x rect-data) - :y (:y rect-data) - :width (:width rect-data) - :height (:height rect-data) - :on-pointer-enter on-pointer-enter - :on-pointer-leave on-pointer-leave - :on-pointer-down on-pointer-down - :on-lost-pointer-capture on-lost-pointer-capture - :on-pointer-move on-pointer-move - :class (when (or hover? selected?) - (if (= (:resize-axis rect-data) :x) (cur/get-dynamic "resize-ew" 0) (cur/get-dynamic "resize-ew" 90))) - :style {:fill (if (or hover? selected?) warning-color "none") - :opacity (if selected? 0.5 0.25)}}])) - -(mf/defc margin-rects [{:keys [shape frame zoom alt? shift?]}] - (let [shape-id (:id shape) - pill-width (/ flex-display-pill-width zoom) - pill-height (/ flex-display-pill-height zoom) - margins-selected (mf/deref refs/workspace-margins-selected) - hover-value (mf/use-var 0) - mouse-pos (mf/use-var nil) - hover (mf/use-var nil) - hover-all? (and (not (nil? @hover)) alt?) - hover-v? (and (or (= @hover :m1) (= @hover :m3)) shift?) - hover-h? (and (or (= @hover :m2) (= @hover :m4)) shift?) - margin (:layout-item-margin shape) - {:keys [width height x1 x2 y1 y2]} (:selrect shape) - on-pointer-enter (fn [hover-type val] - (reset! hover hover-type) - (reset! hover-value val)) - on-pointer-leave #(reset! hover nil) - hover? #(or hover-all? - (and (or (= % :m1) (= % :m3)) hover-v?) - (and (or (= % :m2) (= % :m4)) hover-h?) - (= @hover %)) - margin-display-data {:m1 {:key (str shape-id "-m1") - :x x1 - :y (if (:flip-y frame) y2 (- y1 (:m1 margin))) - :width width - :height (:m1 margin) - :initial-value (:m1 margin) - :resize-type :top - :resize-axis :y - :resize-negate? (:flip-y frame)} - :m2 {:key (str shape-id "-m2") - :x (if (:flip-x frame) (- x1 (:m2 margin)) x2) - :y y1 - :width (:m2 margin) - :height height - :initial-value (:m2 margin) - :resize-type :left - :resize-axis :x - :resize-negate? (:flip-x frame)} - :m3 {:key (str shape-id "-m3") - :x x1 - :y (if (:flip-y frame) (- y1 (:m3 margin)) y2) - :width width - :height (:m3 margin) - :initial-value (:m3 margin) - :resize-type :top - :resize-axis :y - :resize-negate? (:flip-y frame)} - :m4 {:key (str shape-id "-m4") - :x (if (:flip-x frame) x2 (- x1 (:m4 margin))) - :y y1 - :width (:m4 margin) - :height height - :initial-value (:m4 margin) - :resize-type :left - :resize-axis :x - :resize-negate? (:flip-x frame)}}] - - [:g.margins {:pointer-events "visible"} - (for [[margin-num rect-data] margin-display-data] - [:& margin-display - {:key (:key rect-data) - :shape-id shape-id - :zoom zoom - :hover-all? hover-all? - :hover-v? hover-v? - :hover-h? hover-h? - :margin-num margin-num - :margin margin - :on-pointer-enter (partial on-pointer-enter margin-num (get margin margin-num)) - :on-pointer-leave on-pointer-leave - :rect-data rect-data - :hover? (hover? margin-num) - :selected? (get margins-selected margin-num) - :mouse-pos mouse-pos - :hover-value hover-value}]) - - (when @hover - [:& flex-display-pill {:height pill-height - :width pill-width - :font-size (/ font-size zoom) - :border-radius (/ flex-display-pill-border-radius zoom) - :color warning-color - :x (:x @mouse-pos) - :y (- (:y @mouse-pos) pill-width) - :value @hover-value}])])) - -(mf/defc gap-display [{:keys [frame-id zoom gap-type gap on-pointer-enter on-pointer-leave - rect-data hover? selected? mouse-pos hover-value]}] - (let [resizing (mf/use-var nil) - start (mf/use-var nil) - original-value (mf/use-var 0) - negate? (:resize-negate? rect-data) - axis (:resize-axis rect-data) - - on-pointer-down - (mf/use-fn - (mf/deps frame-id gap-type gap) - (fn [event] - (dom/capture-pointer event) - (reset! resizing gap-type) - (reset! start (dom/get-client-position event)) - (reset! original-value (:initial-value rect-data)))) - - on-lost-pointer-capture - (mf/use-fn - (mf/deps frame-id gap-type gap) - (fn [event] - (dom/release-pointer event) - (reset! resizing nil) - (reset! start nil) - (reset! original-value 0) - (st/emit! (dwm/apply-modifiers)))) - - on-pointer-move - (mf/use-fn - (mf/deps frame-id gap-type gap) - (fn [event] - (let [pos (dom/get-client-position event)] - (reset! mouse-pos (point->viewport pos)) - (when (= @resizing gap-type) - (let [delta (-> (gpt/to-vec @start pos) - (cond-> negate? gpt/negate) - (get axis)) - val (int (max (+ @original-value (/ delta zoom)) 0)) - layout-gap (assoc gap gap-type val) - modifiers (dwm/create-modif-tree [frame-id] (ctm/change-property (ctm/empty) :layout-gap layout-gap))] - - (reset! hover-value val) - (st/emit! (dwm/set-modifiers modifiers)))))))] - - [:rect.gap-rect {:x (:x rect-data) - :y (:y rect-data) - :width (:width rect-data) - :height (:height rect-data) - :on-pointer-enter on-pointer-enter - :on-pointer-leave on-pointer-leave - :on-pointer-down on-pointer-down - :on-lost-pointer-capture on-lost-pointer-capture - :on-pointer-move on-pointer-move - :class (when (or hover? selected?) - (if (= (:resize-axis rect-data) :x) (cur/get-dynamic "resize-ew" 0) (cur/get-dynamic "resize-ew" 90))) - :style {:fill (if (or hover? selected?) distance-color "none") - :opacity (if selected? 0.5 0.25)}}])) - -(mf/defc gap-rects [{:keys [frame zoom]}] - (let [frame-id (:id frame) - saved-dir (:layout-flex-dir frame) - is-col? (or (= :column saved-dir) (= :column-reverse saved-dir)) - flip-x (:flip-x frame) - flip-y (:flip-y frame) - pill-width (/ flex-display-pill-width zoom) - pill-height (/ flex-display-pill-height zoom) - workspace-modifiers (mf/deref refs/workspace-modifiers) - gap-selected (mf/deref refs/workspace-gap-selected) - hover (mf/use-var nil) - hover-value (mf/use-var 0) - mouse-pos (mf/use-var nil) - padding (:layout-padding frame) - gap (:layout-gap frame) - {:keys [width height x1 y1]} (:selrect frame) - on-pointer-enter (fn [hover-type val] - (reset! hover hover-type) - (reset! hover-value val)) - - on-pointer-leave #(reset! hover nil) - negate {:column-gap (if flip-x true false) - :row-gap (if flip-y true false)} - - objects (wsh/lookup-page-objects @st/state) - children (->> (cfh/get-immediate-children objects frame-id) - (remove ctl/position-absolute?)) - - children-to-display (if (or (= :row-reverse saved-dir) - (= :column-reverse saved-dir)) - (drop-last children) - (rest children)) - children-to-display (->> children-to-display - (map #(gsh/transform-shape % (get-in workspace-modifiers [(:id %) :modifiers])))) - - wrap-blocks - (let [block-children (->> children - (map #(vector (gpo/parent-coords-bounds (:points %) (:points frame)) %))) - bounds (d/lazy-map (keys objects) #(gsh/shape->points (get objects %))) - - layout-data (gsl/calc-layout-data frame (:points frame) block-children bounds objects) - layout-bounds (:layout-bounds layout-data) - xv #(gpo/start-hv layout-bounds %) - yv #(gpo/start-vv layout-bounds %)] - (for [{:keys [start-p line-width line-height layout-gap-row layout-gap-col num-children]} (:layout-lines layout-data)] - (let [line-width (if is-col? line-width (+ line-width (* (dec num-children) layout-gap-row))) - line-height (if is-col? (+ line-height (* (dec num-children) layout-gap-col)) line-height) - end-p (-> start-p (gpt/add (xv line-width)) (gpt/add (yv line-height)))] - {:x1 (min (:x start-p) (:x end-p)) - :y1 (min (:y start-p) (:y end-p)) - :x2 (max (:x start-p) (:x end-p)) - :y2 (max (:y start-p) (:y end-p))}))) - - block-contains - (fn [x y block] - (if is-col? - (<= (:x1 block) x (:x2 block)) - (<= (:y1 block) y (:y2 block)))) - - get-container-block - (fn [shape] - (let [selrect (:selrect shape) - x (/ (+ (:x1 selrect) (:x2 selrect)) 2) - y (/ (+ (:y1 selrect) (:y2 selrect)) 2)] - (->> wrap-blocks - (filter #(block-contains x y %)) - first))) - - create-cgdd - (fn [shape] - (let [block (get-container-block shape) - x (if flip-x - (- (:x1 (:selrect shape)) - (get-in shape [:layout-item-margin :m2]) - (:column-gap gap)) - (+ (:x2 (:selrect shape)) (get-in shape [:layout-item-margin :m2]))) - y (:y1 block) - h (- (:y2 block) (:y1 block))] - {:x x - :y y - :height h - :width (:column-gap gap) - :initial-value (:column-gap gap) - :resize-type :left - :resize-axis :x - :resize-negate? (:column-gap negate) - :gap-type (if is-col? :row-gap :column-gap)})) - - create-cgdd-block - (fn [block] - (let [x (if flip-x - (- (:x1 block) (:column-gap gap)) - (:x2 block)) - y (if flip-y - (+ y1 (:p3 padding)) - (+ y1 (:p1 padding))) - h (- height (+ (:p1 padding) (:p3 padding)))] - {:x x - :y y - :width (:column-gap gap) - :height h - :initial-value (:column-gap gap) - :resize-type :left - :resize-axis :x - :resize-negate? (:column-gap negate) - :gap-type (if is-col? :column-gap :row-gap)})) - - create-rgdd - (fn [shape] - (let [block (get-container-block shape) - x (:x1 block) - y (if flip-y - (- (:y1 (:selrect shape)) - (get-in shape [:layout-item-margin :m3]) - (:row-gap gap)) - (+ (:y2 (:selrect shape)) (get-in shape [:layout-item-margin :m3]))) - w (- (:x2 block) (:x1 block))] - {:x x - :y y - :width w - :height (:row-gap gap) - :initial-value (:row-gap gap) - :resize-type :bottom - :resize-axis :y - :resize-negate? (:row-gap negate) - :gap-type (if is-col? :row-gap :column-gap)})) - - create-rgdd-block - (fn [block] - (let [x (if flip-x - (+ x1 (:p2 padding)) - (+ x1 (:p4 padding))) - y (if flip-y - (- (:y1 block) (:row-gap gap)) - (:y2 block)) - w (- width (+ (:p2 padding) (:p4 padding)))] - {:x x - :y y - :width w - :height (:row-gap gap) - :initial-value (:row-gap gap) - :resize-type :bottom - :resize-axis :y - :resize-negate? (:row-gap negate) - :gap-type (if is-col? :column-gap :row-gap)})) - - display-blocks (if is-col? - (->> (drop-last wrap-blocks) - (map create-cgdd-block)) - (->> (drop-last wrap-blocks) - (map create-rgdd-block))) - - display-children (if is-col? - (->> children-to-display - (map create-rgdd)) - (->> children-to-display - (map create-cgdd)))] - - [:g.gaps {:pointer-events "visible"} - (for [[index display-item] (d/enumerate (concat display-blocks display-children))] - (let [gap-type (:gap-type display-item)] - [:& gap-display {:key (str frame-id index) - :frame-id frame-id - :zoom zoom - :gap-type gap-type - :gap gap - :on-pointer-enter (partial on-pointer-enter gap-type (get gap gap-type)) - :on-pointer-leave on-pointer-leave - :rect-data display-item - :hover? (= @hover gap-type) - :selected? (= gap-selected gap-type) - :mouse-pos mouse-pos - :hover-value hover-value}])) - - (when @hover - [:& flex-display-pill {:height pill-height - :width pill-width - :font-size (/ font-size zoom) - :border-radius (/ flex-display-pill-border-radius zoom) - :color distance-color - :x (:x @mouse-pos) - :y (- (:y @mouse-pos) pill-width) - :value @hover-value}])])) - -(mf/defc padding - [{:keys [frame zoom alt? shift?]}] - (when frame - [:g.measurement-gaps {:pointer-events "none"} - [:g.hover-shapes - [:& padding-rects {:frame frame :zoom zoom :alt? alt? :shift? shift?}]]])) - -(mf/defc gap - [{:keys [frame zoom]}] - (when frame - [:g.measurement-gaps {:pointer-events "none"} - [:g.hover-shapes - [:& gap-rects {:frame frame :zoom zoom}]]])) - -(mf/defc margin - [{:keys [shape parent zoom alt? shift?]}] - (when shape - [:g.measurement-gaps {:pointer-events "none"} - [:g.hover-shapes - [:& margin-rects {:shape shape :frame parent :zoom zoom :alt? alt? :shift? shift?}]]])) - - diff --git a/frontend/src/app/main/ui/modal.scss b/frontend/src/app/main/ui/modal.scss index dc2c0e2748..6dd5b4eda0 100644 --- a/frontend/src/app/main/ui/modal.scss +++ b/frontend/src/app/main/ui/modal.scss @@ -2,7 +2,6 @@ :global(:root) { --s-4: 0.25rem; - --layer-indentation-size: calc(var(--s-4) * 5); } .modal-wrapper { diff --git a/frontend/src/app/main/ui/viewer.cljs b/frontend/src/app/main/ui/viewer.cljs index 651a7c8061..899dee3863 100644 --- a/frontend/src/app/main/ui/viewer.cljs +++ b/frontend/src/app/main/ui/viewer.cljs @@ -542,12 +542,11 @@ :index index :thumbnail-data (:thumbnails file)}] - [:section {:id "viewer-section" - :ref viewer-section-ref - :data-viewer-section true - :class (stl/css-case :viewer-section true - :fulscreen fullscreen?) - :on-click click-on-screen} + [:section#viewer-section {:ref viewer-section-ref + :data-viewer-section true + :class (stl/css-case :viewer-section true + :fulscreen fullscreen?) + :on-click click-on-screen} (cond (empty? frames) [:section {:class (stl/css :empty-state)} diff --git a/frontend/src/app/main/ui/viewer.scss b/frontend/src/app/main/ui/viewer.scss index 94f54ae5f6..7b98ecfa39 100644 --- a/frontend/src/app/main/ui/viewer.scss +++ b/frontend/src/app/main/ui/viewer.scss @@ -62,6 +62,7 @@ flex-wrap: nowrap; margin-top: 0; height: 100%; + overflow: hidden; } .viewer-go-prev, diff --git a/frontend/src/app/main/ui/viewer/inspect/code.scss b/frontend/src/app/main/ui/viewer/inspect/code.scss index 1e1e58666f..5b6d66c9c3 100644 --- a/frontend/src/app/main/ui/viewer/inspect/code.scss +++ b/frontend/src/app/main/ui/viewer/inspect/code.scss @@ -28,7 +28,6 @@ flex-direction: column; height: 100%; min-height: 0; - overflow: hidden; padding: 0 $s-4 $s-8 0; pre { diff --git a/frontend/src/app/main/ui/viewer/interactions.cljs b/frontend/src/app/main/ui/viewer/interactions.cljs index c304d05451..60f1928c42 100644 --- a/frontend/src/app/main/ui/viewer/interactions.cljs +++ b/frontend/src/app/main/ui/viewer/interactions.cljs @@ -113,24 +113,27 @@ [:& (mf/provider shapes/base-frame-ctx) {:value base} [:& (mf/provider shapes/frame-offset-ctx) {:value offset} ;; We have two different svgs for fixed and not fixed elements so we can emulate the sticky css attribute in svg - [:svg.not-fixed {: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-not-fixed {:shape frame :view-box vbox}]] - [:svg.fixed {: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" - :style {:width (:width size) - :height (:height size)}} - [:& wrapper-fixed {:shape fixed-frame :view-box vbox}]]]])) + [:svg {:class (stl/css :fixed) + :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" + :style {:width (:width size) + :height (:height size)}} + [:& wrapper-fixed {:shape fixed-frame :view-box vbox}]] + + [:svg {:class (stl/css :not-fixed) + :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-not-fixed {:shape frame :view-box vbox}]]]])) (mf/defc viewport {::mf/wrap [mf/memo] diff --git a/frontend/src/app/main/ui/viewer/interactions.scss b/frontend/src/app/main/ui/viewer/interactions.scss index 07b12e3af1..656bc7c937 100644 --- a/frontend/src/app/main/ui/viewer/interactions.scss +++ b/frontend/src/app/main/ui/viewer/interactions.scss @@ -78,3 +78,12 @@ } // breakpoint 1013px + +.fixed { + position: fixed; + pointer-events: none; + + :global(.frame-children) g { + pointer-events: auto; + } +} diff --git a/frontend/src/app/main/ui/workspace.scss b/frontend/src/app/main/ui/workspace.scss index a9e8a35a72..0c7df32056 100644 --- a/frontend/src/app/main/ui/workspace.scss +++ b/frontend/src/app/main/ui/workspace.scss @@ -16,13 +16,14 @@ :global(:root) { --s-4: 0.25rem; - --layer-indentation-size: calc(var(--s-4) * 5); + --layer-indentation-size: calc(var(--s-4) * 6); } .workspace { @extend .new-scrollbar; width: 100vw; - height: 100%; + height: 100vh; + max-height: 100vh; user-select: none; display: grid; grid-template-areas: "left-sidebar viewport right-sidebar"; diff --git a/frontend/src/app/main/ui/workspace/colorpicker.cljs b/frontend/src/app/main/ui/workspace/colorpicker.cljs index 1477d2b6b3..8a361d1d06 100644 --- a/frontend/src/app/main/ui/workspace/colorpicker.cljs +++ b/frontend/src/app/main/ui/workspace/colorpicker.cljs @@ -378,17 +378,21 @@ [{vh :height} position x y] (let [;; picker height in pixels h 510 + ;; Checks for overflow outside the viewport height - overflow-fix (max 0 (+ y (- 50) h (- vh))) + max-y (- vh h) x-pos 325] (cond (or (nil? x) (nil? y)) {:left "auto" :right "16rem" :top "4rem"} - (= position :left) {:left (str (- x x-pos) "px") - :top (str (- y 50 overflow-fix) "px")} + (= position :left) + (if (> y max-y) + {:left (str (- x x-pos) "px") + :bottom "1rem"} + {:left (str (- x x-pos) "px") + :top (str (- y 70) "px")}) :else {:left (str (+ x 80) "px") - :top (str (- y 70 overflow-fix) "px")}))) - + :top (str (- y 70) "px")}))) (mf/defc colorpicker-modal {::mf/register modal/components diff --git a/frontend/src/app/main/ui/workspace/colorpicker.scss b/frontend/src/app/main/ui/workspace/colorpicker.scss index 4abe58b463..f3049f3878 100644 --- a/frontend/src/app/main/ui/workspace/colorpicker.scss +++ b/frontend/src/app/main/ui/workspace/colorpicker.scss @@ -8,7 +8,6 @@ .colorpicker-tooltip { @extend .modal-background; - top: $s-100; left: calc(10 * $s-140); width: auto; } diff --git a/frontend/src/app/main/ui/workspace/context_menu.cljs b/frontend/src/app/main/ui/workspace/context_menu.cljs index cb5e635f85..da56b63388 100644 --- a/frontend/src/app/main/ui/workspace/context_menu.cljs +++ b/frontend/src/app/main/ui/workspace/context_menu.cljs @@ -106,8 +106,9 @@ [:span {:class (stl/css :title)} title] (when shortcut [:span {:class (stl/css :shortcut)} - (for [sc (scd/split-sc shortcut)] - [:span {:class (stl/css :shortcut-key)} sc])]) + (for [[idx sc] (d/enumerate (scd/split-sc shortcut))] + [:span {:key (dm/str shortcut "-" idx) + :class (stl/css :shortcut-key)} sc])]) (when (> (count children) 1) [:span {:class (stl/css :submenu-icon)} i/arrow-refactor]) diff --git a/frontend/src/app/main/ui/workspace/left_header.cljs b/frontend/src/app/main/ui/workspace/left_header.cljs index 25c47f99f8..c5d57c29dc 100644 --- a/frontend/src/app/main/ui/workspace/left_header.cljs +++ b/frontend/src/app/main/ui/workspace/left_header.cljs @@ -7,6 +7,7 @@ (ns app.main.ui.workspace.left-header (:require-macros [app.main.style :as stl]) (:require + [app.common.data.macros :as dm] [app.common.files.helpers :as cfh] [app.common.uuid :as uuid] [app.config :as cf] @@ -625,7 +626,9 @@ (keyword))] (st/emit! (-> (dw/toggle-layout-flag flag) - (vary-meta assoc ::ev/origin "workspace-menu"))))))] + (vary-meta assoc ::ev/origin "workspace-menu"))) + (reset! show-menu* false) + (reset! sub-menu* nil))))] [:* @@ -724,7 +727,7 @@ (mf/defc left-header {::mf/wrap-props false} - [{:keys [file layout project page-id]}] + [{:keys [file layout project page-id class]}] (let [file-id (:id file) file-name (:name file) project-id (:id project) @@ -780,7 +783,7 @@ (mf/with-effect [editing?] (when ^boolean editing? (dom/select-text! (mf/ref-val input-ref)))) - [:header {:class (stl/css :workspace-header-left)} + [:header {:class (dm/str class " " (stl/css :workspace-header-left))} [:a {:on-click go-back :class (stl/css :main-icon)} i/logo-icon] [:div {:alt (tr "workspace.sitemap") diff --git a/frontend/src/app/main/ui/workspace/left_header.scss b/frontend/src/app/main/ui/workspace/left_header.scss index d527a04e9c..6b0e5016e0 100644 --- a/frontend/src/app/main/ui/workspace/left_header.scss +++ b/frontend/src/app/main/ui/workspace/left_header.scss @@ -9,7 +9,6 @@ .workspace-header-left { display: flex; align-items: center; - height: $s-48; padding: $s-8 $s-8 $s-4 $s-8; } diff --git a/frontend/src/app/main/ui/workspace/libraries.cljs b/frontend/src/app/main/ui/workspace/libraries.cljs index 3829f7ca39..8d6b7dcc93 100644 --- a/frontend/src/app/main/ui/workspace/libraries.cljs +++ b/frontend/src/app/main/ui/workspace/libraries.cljs @@ -481,6 +481,11 @@ on-tab-change (mf/use-fn #(reset! selected-tab* %)) + close-dialog-outside + (mf/use-fn (fn [event] + (when (= (dom/get-target event) (dom/get-current-target event)) + (modal/hide!)))) + close-dialog (mf/use-fn (fn [_] (modal/hide!) @@ -490,7 +495,7 @@ (when team-id (st/emit! (dwl/fetch-shared-files {:team-id team-id})))) - [:div {:class (stl/css :modal-overlay)} + [:div {:class (stl/css :modal-overlay) :on-click close-dialog-outside} [:div {:class (stl/css :modal-dialog)} [:button {:class (stl/css :close) :on-click close-dialog} diff --git a/frontend/src/app/main/ui/workspace/libraries.scss b/frontend/src/app/main/ui/workspace/libraries.scss index 8ebf89b741..e91cedb36d 100644 --- a/frontend/src/app/main/ui/workspace/libraries.scss +++ b/frontend/src/app/main/ui/workspace/libraries.scss @@ -15,7 +15,6 @@ width: 100%; z-index: $z-index-modal; background-color: var(--overlay-color); - pointer-events: none; // This is to allow outside click that closes modal. .modal-dialog { position: relative; @@ -25,7 +24,6 @@ padding: $s-32; border-radius: $br-10; background-color: var(--modal-background-color); - pointer-events: all; .close { @extend .button-tertiary; position: absolute; @@ -146,8 +144,10 @@ .section-title { @include titleTipography; + color: var(--modal-title-foreground-color); margin-bottom: $s-12; } + .libraries-search { margin: $s-12 0; .search-icon { diff --git a/frontend/src/app/main/ui/workspace/shapes.cljs b/frontend/src/app/main/ui/workspace/shapes.cljs index ffb8c1d529..d1889e620e 100644 --- a/frontend/src/app/main/ui/workspace/shapes.cljs +++ b/frontend/src/app/main/ui/workspace/shapes.cljs @@ -12,9 +12,11 @@ others are defined using a generic wrapper implemented in common." (:require + [app.common.data :as d] [app.common.data.macros :as dm] [app.common.files.helpers :as cfh] [app.common.geom.rect :as grc] + [app.common.geom.shapes :as gsh] [app.common.uuid :as uuid] [app.main.ui.context :as ctx] [app.main.ui.shapes.circle :as circle] @@ -42,22 +44,34 @@ (def image-wrapper (common/generic-wrapper-factory image/image-shape)) (def rect-wrapper (common/generic-wrapper-factory rect/rect-shape)) +(defn- make-is-frame-overlap + [vbox objects] + (fn [shape] + (let [bounds + (if (dm/get-prop shape :show-content) + (let [children (->> (cfh/get-children-ids objects (dm/get-prop shape :id)) + (map (d/getf objects)))] + (gsh/shapes->rect (cons shape children))) + (dm/get-prop shape :selrect))] + (grc/overlaps-rects? vbox bounds)))) + (mf/defc root-shape "Draws the root shape of the viewport and recursively all the shapes" {::mf/wrap [mf/memo] ::mf/wrap-props false} [props] - (let [objects (obj/get props "objects") - active-frames (obj/get props "active-frames") - shapes (cfh/get-immediate-children objects) - vbox (mf/use-ctx ctx/current-vbox) + (let [objects (obj/get props "objects") + active-frames (obj/get props "active-frames") + shapes (cfh/get-immediate-children objects) + vbox (mf/use-ctx ctx/current-vbox) - shapes (mf/with-memo [shapes vbox] - (if (some? vbox) - (filter (fn [shape] - (grc/overlaps-rects? vbox (dm/get-prop shape :selrect))) - shapes) - shapes))] + frame-overlap? (mf/with-memo [vbox objects] + #(make-is-frame-overlap vbox objects)) + + shapes (mf/with-memo [shapes vbox frame-overlap?] + (cond->> shapes + (some? vbox) + (filter frame-overlap?)))] [:g {:id (dm/str "shape-" uuid/zero)} [:& (mf/provider ctx/active-frames) {:value active-frames} diff --git a/frontend/src/app/main/ui/workspace/shapes/bool.cljs b/frontend/src/app/main/ui/workspace/shapes/bool.cljs index 6a07eeed85..a3080b704e 100644 --- a/frontend/src/app/main/ui/workspace/shapes/bool.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/bool.cljs @@ -11,6 +11,7 @@ [app.main.ui.shapes.bool :as bool] [app.main.ui.shapes.shape :refer [shape-container]] [app.main.ui.workspace.shapes.common :refer [check-shape-props]] + [app.main.ui.workspace.shapes.debug :as wsd] [rumext.v2 :as mf])) (defn bool-wrapper-factory @@ -38,5 +39,7 @@ [:> shape-container {:shape shape} [:& bool-shape {:shape shape - :childs childs}]])))) + :childs childs}] + (when *assert* + [:& wsd/shape-debug {:shape shape}])])))) diff --git a/frontend/src/app/main/ui/workspace/shapes/common.cljs b/frontend/src/app/main/ui/workspace/shapes/common.cljs index c407b8db8f..4af042e877 100644 --- a/frontend/src/app/main/ui/workspace/shapes/common.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/common.cljs @@ -8,6 +8,7 @@ (:require [app.common.record :as cr] [app.main.ui.shapes.shape :refer [shape-container]] + [app.main.ui.workspace.shapes.debug :as wsd] [rumext.v2 :as mf])) (def ^:private excluded-attrs @@ -34,4 +35,6 @@ [props] (let [shape (unchecked-get props "shape")] [:> shape-container {:shape shape} - [:& component {:shape shape}]]))) + [:& component {:shape shape}] + (when *assert* + [:& wsd/shape-debug {:shape shape}])]))) diff --git a/frontend/src/app/main/ui/workspace/shapes/debug.cljs b/frontend/src/app/main/ui/workspace/shapes/debug.cljs new file mode 100644 index 0000000000..71cec7d268 --- /dev/null +++ b/frontend/src/app/main/ui/workspace/shapes/debug.cljs @@ -0,0 +1,99 @@ +;; 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) KALEIDOS INC + +(ns app.main.ui.workspace.shapes.debug + (:require + [app.common.data :as d] + [app.common.data.macros :as dm] + [app.common.files.helpers :as cfh] + [app.common.geom.shapes :as gsh] + [app.common.geom.shapes.text :as gst] + [app.common.math :as mth] + [app.main.refs :as refs] + [app.util.color :as uc] + [app.util.debug :as dbg] + [app.util.dom :as dom] + [cuerdas.core :as str] + [rumext.v2 :as mf])) + +(mf/defc debug-bounding-boxes + [{:keys [shape]}] + (let [points (->> (:points shape) + (map #(dm/fmt "%,%" (dm/get-prop % :x) (dm/get-prop % :y))) + (str/join " ")) + color (mf/use-memo #(uc/random-color)) + sr (:selrect shape)] + [:g.debug-bounding-boxes + [:rect {:transform (gsh/transform-str shape) + :x (:x sr) + :y (:y sr) + :width (:width sr) + :height (:height sr) + :fill color + :opacity 0.2}] + (for [p (:points shape)] + [:circle {:cx (dm/get-prop p :x) + :cy (dm/get-prop p :y) + :r 2 + :fill color}]) + [:polygon {:points points + :stroke-width 1 + :stroke color}]])) + +(mf/defc debug-text-bounds + {::mf/wrap-props false} + [props] + (let [shape (unchecked-get props "shape") + zoom (mf/deref refs/selected-zoom) + bounding-box (gst/shape->rect shape) + ctx (js* "document.createElement(\"canvas\").getContext(\"2d\")")] + [:g {:transform (gsh/transform-str shape)} + [:rect {:x (:x bounding-box) + :y (:y bounding-box) + :width (:width bounding-box) + :height (:height bounding-box) + :style {:fill "none" + :stroke "orange" + :stroke-width (/ 1 zoom)}}] + + (for [[index data] (d/enumerate (:position-data shape))] + (let [{:keys [x y width height]} data + res (dom/measure-text ctx (:font-size data) (:font-family data) (:text data))] + [:g {:key (dm/str index)} + ;; Text fragment bounding box + [:rect {:x x + :y (- y height) + :width width + :height height + :style {:fill "none" + :stroke "red" + :stroke-width (/ 1 zoom)}}] + + ;; Text baseline + [:line {:x1 (mth/round x) + :y1 (mth/round (- (:y data) (:height data))) + :x2 (mth/round (+ x width)) + :y2 (mth/round (- (:y data) (:height data))) + :style {:stroke "blue" + :stroke-width (/ 1 zoom)}}] + + [:line {:x1 (:x data) + :y1 (- (:y data) (:descent res)) + :x2 (+ (:x data) (:width data)) + :y2 (- (:y data) (:descent res)) + :style {:stroke "green" + :stroke-width (/ 2 zoom)}}]]))])) + +(mf/defc shape-debug + [{:keys [shape]}] + [:* + (when ^boolean (dbg/enabled? :bounding-boxes) + [:& debug-bounding-boxes]) + + (when (and ^boolean (cfh/text-shape? shape) + ^boolean (dbg/enabled? :text-outline) + ^boolean (seq (:position-data shape))) + [:& debug-text-bounds {:shape shape}])]) diff --git a/frontend/src/app/main/ui/workspace/shapes/frame.cljs b/frontend/src/app/main/ui/workspace/shapes/frame.cljs index 2dc7e32890..9ecddd1e20 100644 --- a/frontend/src/app/main/ui/workspace/shapes/frame.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/frame.cljs @@ -20,6 +20,7 @@ [app.main.ui.shapes.frame :as frame] [app.main.ui.shapes.shape :refer [shape-container]] [app.main.ui.workspace.shapes.common :refer [check-shape-props]] + [app.main.ui.workspace.shapes.debug :as wsd] [app.main.ui.workspace.shapes.frame.dynamic-modifiers :as fdm] [app.util.debug :as dbg] [app.util.dom :as dom] @@ -193,5 +194,8 @@ [:g.frame-content {:id (dm/str "frame-content-" frame-id) :ref container-ref} - [:& frame-shape {:shape shape :ref content-ref}]])]])))) + [:& frame-shape {:shape shape :ref content-ref}]])] + + (when *assert* + [:& wsd/shape-debug {:shape shape}])])))) diff --git a/frontend/src/app/main/ui/workspace/shapes/group.cljs b/frontend/src/app/main/ui/workspace/shapes/group.cljs index 5fbd2ad16b..d98d58a2f7 100644 --- a/frontend/src/app/main/ui/workspace/shapes/group.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/group.cljs @@ -11,6 +11,7 @@ [app.main.ui.shapes.group :as group] [app.main.ui.shapes.shape :refer [shape-container]] [app.main.ui.workspace.shapes.common :refer [check-shape-props]] + [app.main.ui.workspace.shapes.debug :as wsd] [rumext.v2 :as mf])) (defn group-wrapper-factory @@ -30,5 +31,7 @@ [:> shape-container {:shape shape} [:& group-shape {:shape shape - :childs childs}]])))) + :childs childs}] + (when *assert* + [:& wsd/shape-debug {:shape shape}])])))) diff --git a/frontend/src/app/main/ui/workspace/shapes/path.cljs b/frontend/src/app/main/ui/workspace/shapes/path.cljs index b20f65b1da..110238be4b 100644 --- a/frontend/src/app/main/ui/workspace/shapes/path.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/path.cljs @@ -11,6 +11,7 @@ [app.main.refs :as refs] [app.main.ui.shapes.path :as path] [app.main.ui.shapes.shape :refer [shape-container]] + [app.main.ui.workspace.shapes.debug :as wsd] [app.main.ui.workspace.shapes.path.common :as pc] [rumext.v2 :as mf])) @@ -38,4 +39,6 @@ [:> shape-container {:shape shape :pointer-events (when editing? "none")} - [:& path/path-shape {:shape shape}]])) + [:& path/path-shape {:shape shape}] + (when *assert* + [:& wsd/shape-debug {:shape shape}])])) diff --git a/frontend/src/app/main/ui/workspace/shapes/svg_raw.cljs b/frontend/src/app/main/ui/workspace/shapes/svg_raw.cljs index 774fa61faa..de1701e016 100644 --- a/frontend/src/app/main/ui/workspace/shapes/svg_raw.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/svg_raw.cljs @@ -10,6 +10,7 @@ [app.main.refs :as refs] [app.main.ui.shapes.shape :refer [shape-container]] [app.main.ui.shapes.svg-raw :as svg-raw] + [app.main.ui.workspace.shapes.debug :as wsd] [rumext.v2 :as mf])) (defn svg-raw-wrapper-factory @@ -26,7 +27,9 @@ (if (contains? csvg/svg-group-safe-tags svg-tag) [:> shape-container {:shape shape} [:& svg-raw-shape {:shape shape - :childs childs}]] + :childs childs}] + (when *assert* + [:& wsd/shape-debug {:shape shape}])] [:& svg-raw-shape {:shape shape :childs childs}]))))) diff --git a/frontend/src/app/main/ui/workspace/shapes/text.cljs b/frontend/src/app/main/ui/workspace/shapes/text.cljs index eb9b5e68a6..cdaeda400f 100644 --- a/frontend/src/app/main/ui/workspace/shapes/text.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/text.cljs @@ -6,63 +6,14 @@ (ns app.main.ui.workspace.shapes.text (:require - [app.common.data :as d] [app.common.data.macros :as dm] - [app.common.geom.shapes :as gsh] - [app.common.geom.shapes.text :as gst] - [app.common.math :as mth] [app.main.data.workspace.texts :as dwt] [app.main.refs :as refs] [app.main.ui.shapes.shape :refer [shape-container]] [app.main.ui.shapes.text :as text] - [app.util.debug :as dbg] - [app.util.dom :as dom] + [app.main.ui.workspace.shapes.debug :as wsd] [rumext.v2 :as mf])) -(mf/defc debug-text-bounds - {::mf/wrap-props false} - [props] - (let [shape (unchecked-get props "shape") - zoom (mf/deref refs/selected-zoom) - bounding-box (gst/shape->rect shape) - ctx (js* "document.createElement(\"canvas\").getContext(\"2d\")")] - [:g {:transform (gsh/transform-str shape)} - [:rect {:x (:x bounding-box) - :y (:y bounding-box) - :width (:width bounding-box) - :height (:height bounding-box) - :style {:fill "none" - :stroke "orange" - :stroke-width (/ 1 zoom)}}] - - (for [[index data] (d/enumerate (:position-data shape))] - (let [{:keys [x y width height]} data - res (dom/measure-text ctx (:font-size data) (:font-family data) (:text data))] - [:g {:key (dm/str index)} - ;; Text fragment bounding box - [:rect {:x x - :y (- y height) - :width width - :height height - :style {:fill "none" - :stroke "red" - :stroke-width (/ 1 zoom)}}] - - ;; Text baseline - [:line {:x1 (mth/round x) - :y1 (mth/round (- (:y data) (:height data))) - :x2 (mth/round (+ x width)) - :y2 (mth/round (- (:y data) (:height data))) - :style {:stroke "blue" - :stroke-width (/ 1 zoom)}}] - - [:line {:x1 (:x data) - :y1 (- (:y data) (:descent res)) - :x2 (+ (:x data) (:width data)) - :y2 (- (:y data) (:descent res)) - :style {:stroke "green" - :stroke-width (/ 2 zoom)}}]]))])) - ;; --- Text Wrapper for workspace (mf/defc text-wrapper {::mf/wrap-props false} @@ -84,6 +35,5 @@ [:g.text-shape {:key (dm/str shape-id)} [:& text/text-shape {:shape shape}]] - (when (and ^boolean (dbg/enabled? :text-outline) - ^boolean (seq (:position-data shape))) - [:& debug-text-bounds {:shape shape}])])) + (when *assert* + [:& wsd/shape-debug {:shape shape}])])) diff --git a/frontend/src/app/main/ui/workspace/sidebar.cljs b/frontend/src/app/main/ui/workspace/sidebar.cljs index 22201e991f..db497e7bd0 100644 --- a/frontend/src/app/main/ui/workspace/sidebar.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar.cljs @@ -41,6 +41,7 @@ section (cond (or mode-inspect? (contains? layout :layers)) :layers (contains? layout :assets) :assets) + shortcuts? (contains? layout :shortcuts) show-debug? (contains? layout :debug-panel) @@ -65,44 +66,54 @@ :global/three-row (and (> size 300) (<= size 400)) :global/four-row (> size 400)) :style #js {"--width" (dm/str size "px")}} - [:& left-header {:file file :layout layout :project project :page-id page-id}] + + [:& left-header {:file file :layout layout :project project :page-id page-id + :class (stl/css :left-header)}] + [:div {:on-pointer-down on-pointer-down :on-lost-pointer-capture on-lost-pointer-capture :on-pointer-move on-pointer-move :class (stl/css :resize-area)}] - [:div {:class (stl/css :settings-bar-inside)} + [:* (cond (true? shortcuts?) - [:& shortcuts-container] + [:& shortcuts-container {:class (stl/css :settings-bar-content)}] (true? show-debug?) - [:& debug-panel] + [:& debug-panel {:class (stl/css :settings-bar-content)}] :else - [:div {:class (stl/css :tabs-wrapper)} + [:div {:class (stl/css :settings-bar-content)} [:& tab-container {:on-change-tab on-tab-change :selected section :collapsable true :handle-collapse handle-collapse :header-class (stl/css :tab-spacing)} - [:& tab-element {:id :layers :title (tr "workspace.sidebar.layers")} - [:div {:class (stl/css :layers-tab) - :style #js {"--height" (str size-pages "px")}} + + [:& tab-element {:id :layers + :title (tr "workspace.sidebar.layers")} + [:article {:class (stl/css :layers-tab) + :style #js {"--height" (str size-pages "px")}} + [:& sitemap {:layout layout :toggle-pages toggle-pages :show-pages? @show-pages? :size size-pages}] + (when @show-pages? [:div {:class (stl/css :resize-area-horiz) :on-pointer-down on-pointer-down-pages :on-lost-pointer-capture on-lost-pointer-capture-pages :on-pointer-move on-pointer-move-pages}]) + [:& layers-toolbox {:size-parent size :size size-pages}]]] + (when-not ^boolean mode-inspect? - [:& tab-element {:id :assets :title (tr "workspace.toolbar.assets")} + [:& tab-element {:id :assets + :title (tr "workspace.toolbar.assets")} [:& assets-toolbox]])]])]])) ;; --- Right Sidebar (Component) diff --git a/frontend/src/app/main/ui/workspace/sidebar.scss b/frontend/src/app/main/ui/workspace/sidebar.scss index e4775f0d74..8f006ca43e 100644 --- a/frontend/src/app/main/ui/workspace/sidebar.scss +++ b/frontend/src/app/main/ui/workspace/sidebar.scss @@ -10,52 +10,49 @@ $width-settings-bar: $s-276; $width-settings-bar-max: $s-500; .left-settings-bar { + display: grid; + grid-template-areas: + "header header" + "content resize"; + grid-template-rows: $s-48 1fr; + grid-template-columns: 1fr 0; position: relative; grid-area: left-sidebar; min-width: $width-settings-bar; max-width: $width-settings-bar-max; width: var(--width, $width-settings-bar); - height: 100%; background-color: var(--panel-background-color); + height: 100vh; + max-height: 100vh; .resize-area { - position: absolute; - right: calc(-1 * $s-8); - z-index: $z-index-3; - width: $s-8; - height: calc(100vh - $s-52); - cursor: ew-resize; - } - .resize-area-horiz { - position: absolute; - top: calc($s-80 + var(--height, 200px)); - left: 0; - width: 100%; - height: $s-12; - border-top: $s-2 solid var(--resize-area-border-color); - background-color: var(--resize-area-background-color); - cursor: ns-resize; - } - .settings-bar-inside { - display: grid; - grid-template-columns: 100%; - grid-template-rows: 100%; - height: calc(100vh - $s-52); - overflow: hidden; - .tabs-wrapper { - .layers-tab { - display: grid; - grid-template-rows: auto 1fr; - grid-template-columns: 100%; - height: 100%; - overflow: hidden; - } - } + grid-area: resize; } } + +.left-header { + grid-area: header; +} + +.settings-bar-content { + grid-area: content; + right: calc(-1 * $s-8); +} + +.resize-area { + position: absolute; + top: 0; + left: unset; + z-index: $z-index-3; + width: $s-8; + cursor: ew-resize; + height: 100%; +} + .tab-spacing { - margin: $s-4 $s-8 0 $s-8; + margin-inline: $s-8; } + .right-settings-bar { grid-area: right-sidebar; width: $width-settings-bar; @@ -69,14 +66,24 @@ $width-settings-bar-max: $s-500; &.expanded { width: var(--width, $width-settings-bar); } - .resize-area { - position: absolute; - height: 100%; - width: $s-8; - z-index: $z-index-3; - cursor: ew-resize; - } + .settings-bar-inside { + display: grid; + grid-template-columns: 100%; + grid-template-rows: 100%; + height: calc(100vh - $s-52); + overflow: hidden; } } + +.resize-area-horiz { + position: absolute; + top: calc($s-80 + var(--height, 200px)); + left: 0; + width: 100%; + height: $s-12; + border-top: $s-2 solid var(--resize-area-border-color); + background-color: var(--resize-area-background-color); + cursor: ns-resize; +} diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs index 09118cffa6..d82ae14e11 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs @@ -153,7 +153,7 @@ :option-handler on-section-filter-change :data-test "typographies"}]))] - [:div {:class (stl/css :assets-bar)} + [:article {:class (stl/css :assets-bar)} [:div {:class (stl/css :assets-header)} (when-not ^boolean read-only? [:button {:class (stl/css :libraries-button) @@ -178,7 +178,7 @@ :fixed? true :min-width? true :top 152 - :left 64 + :left 18 :options options :workspace? true}] [:button {:class (stl/css :sort-button) @@ -190,6 +190,6 @@ [:& (mf/provider cmm/assets-filters) {:value filters} [:& (mf/provider cmm/assets-toggle-ordering) {:value toggle-ordering} [:& (mf/provider cmm/assets-toggle-list-style) {:value toggle-list-style} - [:div {:class (stl/css :libraries-wrapper)} + [:* [:& assets-local-library {:filters filters}] [:& assets-libraries {:filters filters}]]]]]])) diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets.scss b/frontend/src/app/main/ui/workspace/sidebar/assets.scss index dd8ebf217a..0c73974ca1 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets.scss +++ b/frontend/src/app/main/ui/workspace/sidebar/assets.scss @@ -7,9 +7,11 @@ @import "refactor/common-refactor.scss"; .assets-bar { - position: relative; + display: grid; height: 100%; - overflow: hidden; + grid-auto-rows: max-content; + // TODO: ugly hack :( Fix this! we shouldn't be hardcoding this height + max-height: calc(100vh - $s-80); } .libraries-button { @@ -108,16 +110,6 @@ @include buttonStyle; } -.libraries-wrapper { - overflow-x: hidden; - overflow-y: auto; - scrollbar-gutter: stable; - display: flex; - flex-direction: column; - padding-left: $s-8; - height: calc(100vh - $s-180); -} - .assets-header { padding: $s-8 $s-12 $s-12 $s-12; } diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets/file_library.scss b/frontend/src/app/main/ui/workspace/sidebar/assets/file_library.scss index 0044dbcd18..a131dd26b3 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets/file_library.scss +++ b/frontend/src/app/main/ui/workspace/sidebar/assets/file_library.scss @@ -6,7 +6,14 @@ @import "refactor/common-refactor.scss"; .tool-window { - margin-bottom: $s-24; + padding-inline-start: $s-12; + overflow-y: auto; + display: grid; + grid-auto-rows: max-content; + scrollbar-gutter: stable; + &:last-child { + margin-block-end: $s-24; + } } .file-name { @@ -38,13 +45,7 @@ } .library-content { - display: flex; - flex-direction: column; - height: calc(100% - $s-36); width: 100%; - overflow-y: hidden; - overflow-x: hidden; - margin-top: $s-4; } .asset-title { diff --git a/frontend/src/app/main/ui/workspace/sidebar/debug.cljs b/frontend/src/app/main/ui/workspace/sidebar/debug.cljs index 71adc0d998..b49371e5ff 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/debug.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/debug.cljs @@ -8,6 +8,7 @@ (:require-macros [app.main.style :as stl]) (:require [app.common.data :as d] + [app.common.data.macros :as dm] [app.main.data.workspace :as dw] [app.main.store :as st] [app.main.ui.icons :as i] @@ -16,21 +17,21 @@ [rumext.v2 :as mf])) (mf/defc debug-panel - [] + [{:keys [class] :as props}] (let [on-toggle-enabled (mf/use-fn (fn [event option] (dom/prevent-default event) (dom/stop-propagation event) (dbg/toggle! option) - (js* "app.main.reinit()"))) + (js* "app.main.reinit(true)"))) handle-close (mf/use-fn (fn [] (st/emit! (dw/remove-layout-flag :debug-panel))))] - [:div {:class (stl/css :debug-panel)} + [:div {:class (dm/str class " " (stl/css :debug-panel))} [:div {:class (stl/css :panel-title)} [:span "Debugging tools"] [:div {:class (stl/css :close-button) :on-click handle-close} diff --git a/frontend/src/app/main/ui/workspace/sidebar/layer_item.scss b/frontend/src/app/main/ui/workspace/sidebar/layer_item.scss index 4f703090b9..68779f911b 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/layer_item.scss +++ b/frontend/src/app/main/ui/workspace/sidebar/layer_item.scss @@ -74,11 +74,12 @@ } .element-list-body { - display: flex; align-items: center; + display: grid; + grid-template-columns: auto 1fr auto; + column-gap: $s-4; height: $s-32; width: calc(100% - (var(--depth) * var(--layer-indentation-size))); - padding-right: $s-12; cursor: pointer; &.filtered { @@ -111,7 +112,7 @@ width: $s-16; height: 100%; width: $s-24; - padding: 0 $s-8 0 $s-4; + padding-inline-start: $s-4; svg { @extend .button-icon-small; stroke: var(--icon-foreground); @@ -169,7 +170,7 @@ align-items: center; height: 100%; width: $s-24; - padding: 0 $s-4 0 $s-8; + padding-inline-start: $s-8; svg { @extend .button-icon-small; diff --git a/frontend/src/app/main/ui/workspace/sidebar/layers.cljs b/frontend/src/app/main/ui/workspace/sidebar/layers.cljs index b306434959..4ce3d31968 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/layers.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/layers.cljs @@ -458,7 +458,7 @@ (mf/use-fn #(st/emit! (dw/toggle-focus-mode)))] - [:div#layers {:class (stl/css :layers)} + [:div#layers (if (d/not-empty? focus) [:div {:class (stl/css :tool-window-bar)} [:button {:class (stl/css :focus-title) diff --git a/frontend/src/app/main/ui/workspace/sidebar/layers.scss b/frontend/src/app/main/ui/workspace/sidebar/layers.scss index 71289de1a0..e46c73d23c 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/layers.scss +++ b/frontend/src/app/main/ui/workspace/sidebar/layers.scss @@ -6,14 +6,6 @@ @import "refactor/common-refactor.scss"; -.layers { - position: relative; - display: flex; - flex-direction: column; - overflow: auto; - box-sizing: border-box; -} - .tool-window-bar { display: flex; align-items: center; @@ -246,7 +238,8 @@ } .tool-window-content { - --calculated-height: calc($s-128 + var(--height, $s-200)); + // TODO: sass variables are not being interpolated here, find why + --calculated-height: calc(128px + var(--height, 200px)); display: flex; flex-direction: column; height: calc(100vh - var(--calculated-height)); diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs index dff6d0888e..fd5e423246 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs @@ -555,7 +555,8 @@ :on-change #(set-justify % type) :name (dm/str "grid-justify-items-" (d/name type))} (for [justify [:start :center :end :space-around :space-between :stretch]] - [:& radio-button {:value (d/name justify) + [:& radio-button {:key (dm/str "justify-item-" (d/name justify)) + :value (d/name justify) :icon (get-layout-grid-icon-refactor :justify-items justify is-col?) :title (dm/str "Justify items " (d/name justify)) :id (dm/str "justify-items-" (d/name justify) "-" (d/name type))}])])) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/svg_attrs.scss b/frontend/src/app/main/ui/workspace/sidebar/options/menus/svg_attrs.scss index 536f589c99..036c14ca10 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/svg_attrs.scss +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/svg_attrs.scss @@ -64,6 +64,7 @@ font-size: $fs-10; text-transform: uppercase; margin-inline-start: $s-4; + color: $df-primary; } .attr-row { diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.scss b/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.scss index 6f7aab290d..e8a81f607f 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.scss +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.scss @@ -356,22 +356,22 @@ flex-direction: column; position: relative; margin-bottom: $s-2; + background-color: var(--dropdown-background-color); .title { @include tabTitleTipography; margin: 9px 17px; + color: var(--title-foreground-color); } } .fonts-list { @include menuShadow; - position: absolute; - top: $s-36; - left: 0; + position: relative; display: flex; flex-direction: column; flex: 1 1 auto; min-height: 100%; - height: $s-216; width: 100%; + height: 100%; padding: $s-2; border-radius: $br-8; background-color: var(--dropdown-background-color); diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/rows/color_row.scss b/frontend/src/app/main/ui/workspace/sidebar/options/rows/color_row.scss index 5285303c5f..bfa232fdad 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/rows/color_row.scss +++ b/frontend/src/app/main/ui/workspace/sidebar/options/rows/color_row.scss @@ -49,13 +49,9 @@ } .color-name { @include titleTipography; - display: flex; - align-items: center; - height: $s-28; - padding-left: $s-6; + @include textEllipsis; + padding-inline: $s-6; border-radius: $br-8; - width: 100%; - flex-grow: 1; color: var(--input-foreground-color-active); } .detach-btn { diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/rows/stroke_row.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/rows/stroke_row.cljs index ca0b4b57fe..23c70c23e5 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/rows/stroke_row.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/rows/stroke_row.cljs @@ -123,14 +123,14 @@ stroke-caps-options [{:value nil :label (tr "workspace.options.stroke-cap.none")} :separator - {:value :line-arrow :label (tr "workspace.options.stroke-cap.line-arrow-short")} - {:value :triangle-arrow :label (tr "workspace.options.stroke-cap.triangle-arrow-short")} - {:value :square-marker :label (tr "workspace.options.stroke-cap.square-marker-short")} - {:value :circle-marker :label (tr "workspace.options.stroke-cap.circle-marker-short")} - {:value :diamond-marker :label (tr "workspace.options.stroke-cap.diamond-marker-short")} + {:value :line-arrow :label (tr "workspace.options.stroke-cap.line-arrow-short") :icon :cap-line-arrow} + {:value :triangle-arrow :label (tr "workspace.options.stroke-cap.triangle-arrow-short") :icon :cap-triangle-arrow} + {:value :square-marker :label (tr "workspace.options.stroke-cap.square-marker-short") :icon :cap-square-marker} + {:value :circle-marker :label (tr "workspace.options.stroke-cap.circle-marker-short") :icon :cap-circle-marker} + {:value :diamond-marker :label (tr "workspace.options.stroke-cap.diamond-marker-short") :icon :cap-diamond-marker} :separator - {:value :round :label (tr "workspace.options.stroke-cap.round")} - {:value :square :label (tr "workspace.options.stroke-cap.square")}] + {:value :round :label (tr "workspace.options.stroke-cap.round") :icon :cap-round} + {:value :square :label (tr "workspace.options.stroke-cap.square") :icon :cap-square}] on-cap-switch (mf/use-callback diff --git a/frontend/src/app/main/ui/workspace/sidebar/shortcuts.cljs b/frontend/src/app/main/ui/workspace/sidebar/shortcuts.cljs index 04748ef406..241b88445c 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/shortcuts.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/shortcuts.cljs @@ -341,7 +341,7 @@ :filter-term filter-term}]]]))) (mf/defc shortcuts-container - [] + [{:keys [class] :as props}] (let [workspace-shortcuts app.main.data.workspace.shortcuts/shortcuts path-shortcuts app.main.data.workspace.path.shortcuts/shortcuts all-workspace-shortcuts (->> (d/deep-merge path-shortcuts workspace-shortcuts) @@ -468,7 +468,7 @@ (mf/with-effect [] (dom/focus! (dom/get-element "shortcut-search"))) - [:div {:class (stl/css :shortcuts)} + [:div {:class (dm/str class " " (stl/css :shortcuts))} [:div {:class (stl/css :shortcuts-header)} [:div {:class (stl/css :shortcuts-title)} (tr "shortcuts.title")] [:div {:class (stl/css :shortcuts-close-button) diff --git a/frontend/src/app/main/ui/workspace/top_toolbar.scss b/frontend/src/app/main/ui/workspace/top_toolbar.scss index de44be4431..03cfac6970 100644 --- a/frontend/src/app/main/ui/workspace/top_toolbar.scss +++ b/frontend/src/app/main/ui/workspace/top_toolbar.scss @@ -16,7 +16,7 @@ height: $s-56; padding: $s-8 $s-16; border-radius: $s-8; - z-index: $z-index-2; + z-index: $z-index-10; background-color: var(--color-background-primary); transition: top 0.3s, diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index 35cb52be76..7fdc82f6c8 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -16,6 +16,7 @@ [app.main.data.workspace.modifiers :as dwm] [app.main.refs :as refs] [app.main.ui.context :as ctx] + [app.main.ui.flex-controls :as mfc] [app.main.ui.hooks :as ui-hooks] [app.main.ui.measurements :as msr] [app.main.ui.shapes.export :as use] @@ -232,31 +233,32 @@ disabled-guides? (or drawing-tool transform drawing-path? node-editing?) - one-selected-shape? (= (count selected-shapes) 1) + single-select? (= (count selected-shapes) 1) - show-padding? (and (nil? transform) - one-selected-shape? - (= (:type (first selected-shapes)) :frame) - (= (:layout (first selected-shapes)) :flex) - (zero? (:rotation (first selected-shapes)))) + first-shape (first selected-shapes) + show-padding? + (and (nil? transform) + single-select? + (= (:type first-shape) :frame) + (= (:layout first-shape) :flex) + (zero? (:rotation first-shape))) - show-margin? (and (nil? transform) - one-selected-shape? - (= (:layout selected-frame) :flex) - (zero? (:rotation (first selected-shapes)))) + show-margin? + (and (nil? transform) + single-select? + (= (:layout selected-frame) :flex) + (zero? (:rotation first-shape))) - first-selected-shape (first selected-shapes) - selecting-first-level-frame? (and one-selected-shape? - (cfh/root-frame? first-selected-shape)) + selecting-first-level-frame? (and single-select? (cfh/root-frame? first-shape)) offset-x (if selecting-first-level-frame? - (:x first-selected-shape) + (:x first-shape) (:x selected-frame)) offset-y (if selecting-first-level-frame? - (:y (first selected-shapes)) + (:y first-shape) (:y selected-frame)) rule-area-size (/ rules/rule-area-size zoom)] @@ -439,24 +441,28 @@ :zoom zoom}]) (when show-padding? - [:* - [:& msr/padding - {:frame (first selected-shapes) - :hover @frame-hover - :zoom zoom - :alt? @alt? - :shift? @shift?}] + [:& mfc/padding-control + {:frame first-shape + :hover @frame-hover + :zoom zoom + :alt? @alt? + :shift? @shift? + :on-move-selected on-move-selected + :on-context-menu on-menu-selected}]) - [:& msr/gap - {:frame (first selected-shapes) - :hover @frame-hover - :zoom zoom - :alt? @alt? - :shift? @shift?}]]) + (when show-padding? + [:& mfc/gap-control + {:frame first-shape + :hover @frame-hover + :zoom zoom + :alt? @alt? + :shift? @shift? + :on-move-selected on-move-selected + :on-context-menu on-menu-selected}]) (when show-margin? - [:& msr/margin - {:shape (first selected-shapes) + [:& mfc/margin-control + {:shape first-shape :parent selected-frame :hover @frame-hover :zoom zoom diff --git a/frontend/src/app/main/ui/workspace/viewport/actions.cljs b/frontend/src/app/main/ui/workspace/viewport/actions.cljs index 64fe00e77f..3a9174a9df 100644 --- a/frontend/src/app/main/ui/workspace/viewport/actions.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/actions.cljs @@ -43,7 +43,6 @@ (mf/deps id blocked hidden type selected edition drawing-tool text-editing? node-editing? grid-editing? drawing-path? create-comment? @z? @space? panning workspace-read-only?) - (fn [bevent] ;; We need to handle editor related stuff here because ;; handling on editor dom node does not works properly. @@ -127,6 +126,7 @@ (not mod?) (not shift?) (not @space?)) + (dom/prevent-default bevent) (dom/stop-propagation bevent) (when-not (or workspace-read-only? @z?) diff --git a/frontend/src/app/main/ui/workspace/viewport/rules.cljs b/frontend/src/app/main/ui/workspace/viewport/rules.cljs index c3af821de1..21db6a320a 100644 --- a/frontend/src/app/main/ui/workspace/viewport/rules.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/rules.cljs @@ -23,8 +23,9 @@ (def rules-background "var(--panel-background-color)") (def selection-area-color "var(--color-primary)") (def selection-area-opacity 0.3) -(def over-number-size 50) -(def over-number-opacity 0.7) +(def over-number-size 100) +(def over-number-opacity 0.8) +(def over-number-percent 0.75) (def font-size 12) (def font-family "worksans") @@ -204,7 +205,29 @@ ;; When using the format-number callls we consider if the guide is associated to a frame and we show the position relative to it with the offset (let [rules-background rules-background] [:g.selection-area + [:defs + [:linearGradient {:id "selection-gradient-start"} + [:stop {:offset "0%" :stop-color rules-background :stop-opacity 0}] + [:stop {:offset "40%" :stop-color rules-background :stop-opacity 1}] + [:stop {:offset "100%" :stop-color rules-background :stop-opacity 1}]] + + [:linearGradient {:id "selection-gradient-end"} + [:stop {:offset "0%" :stop-color rules-background :stop-opacity 1}] + [:stop {:offset "60%" :stop-color rules-background :stop-opacity 1}] + [:stop {:offset "100%" :stop-color rules-background :stop-opacity 0}]]] [:g + [:rect {:x (- (:x selection-rect) (* (* over-number-size over-number-percent) zoom-inverse)) + :y (:y vbox) + :width (* over-number-size zoom-inverse) + :height (* rule-area-size zoom-inverse) + :fill "url('#selection-gradient-start')"}] + + [:rect {:x (- (:x2 selection-rect) (* over-number-size (- 1 over-number-percent))) + :y (:y vbox) + :width (* over-number-size zoom-inverse) + :height (* rule-area-size zoom-inverse) + :fill "url('#selection-gradient-end')"}] + [:rect {:x (:x selection-rect) :y (:y vbox) :width (:width selection-rect) @@ -212,15 +235,8 @@ :style {:fill selection-area-color :fill-opacity selection-area-opacity}}] - [:rect {:x (- (:x selection-rect) (* over-number-size zoom-inverse)) - :y (:y vbox) - :width (* over-number-size zoom-inverse) - :height (* rule-area-size zoom-inverse) - :style {:fill rules-background - :fill-opacity over-number-opacity}}] - [:text {:x (- (:x1 selection-rect) (* 4 zoom-inverse)) - :y (+ (:y vbox) (* 12 zoom-inverse)) + :y (+ (:y vbox) (* 10.6 zoom-inverse)) :text-anchor "end" :dominant-baseline "middle" :style {:font-size (* font-size zoom-inverse) @@ -228,15 +244,8 @@ :fill selection-area-color}} (fmt/format-number (- (:x1 selection-rect) offset-x))] - [:rect {:x (:x2 selection-rect) - :y (:y vbox) - :width (* over-number-size zoom-inverse) - :height (* rule-area-size zoom-inverse) - :style {:fill rules-background - :fill-opacity over-number-opacity}}] - [:text {:x (+ (:x2 selection-rect) (* 4 zoom-inverse)) - :y (+ (:y vbox) (* 12 zoom-inverse)) + :y (+ (:y vbox) (* 10.6 zoom-inverse)) :text-anchor "start" :dominant-baseline "middle" :style {:font-size (* font-size zoom-inverse) diff --git a/frontend/src/app/util/color.cljs b/frontend/src/app/util/color.cljs index d7fc4a2bd3..79989b3894 100644 --- a/frontend/src/app/util/color.cljs +++ b/frontend/src/app/util/color.cljs @@ -12,6 +12,8 @@ (:require [app.common.colors :as cc] [app.common.data :as d] + [app.common.data.macros :as dm] + [app.common.math :as mth] [app.util.i18n :as i18n :refer [tr]] [cuerdas.core :as str])) @@ -82,3 +84,10 @@ (:name color) (:color color) (gradient-type->string (:type (:gradient color))))) + +(defn random-color + [] + (dm/fmt "rgb(%, %, %)" + (mth/floor (* (js/Math.random) 256)) + (mth/floor (* (js/Math.random) 256)) + (mth/floor (* (js/Math.random) 256)))) diff --git a/frontend/src/app/util/dom.cljs b/frontend/src/app/util/dom.cljs index c6b3947d58..efe5108685 100644 --- a/frontend/src/app/util/dom.cljs +++ b/frontend/src/app/util/dom.cljs @@ -433,6 +433,12 @@ [o prop] (.getPropertyValue ^js o prop)) +(defn get-css-variable + ([variable element] + (.getPropertyValue (.getComputedStyle js/window element) variable)) + ([variable] + (.getPropertyValue (.getComputedStyle js/window (.-documentElement js/document)) variable))) + (defn focus! [^js node] (when (some? node)