Merge remote-tracking branch 'origin/staging' into develop

This commit is contained in:
Andrey Antukh 2024-01-19 14:44:14 +01:00
commit 098e33bd98
99 changed files with 1545 additions and 1079 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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])))))

View File

@ -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)

View File

@ -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)))))

View File

@ -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

View File

@ -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."

View File

@ -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))

View File

@ -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]]

View File

@ -1 +0,0 @@
<svg width="16" height="6" xmlns="http://www.w3.org/2000/svg"><rect rx="6" ry="6" x="10" width="6" height="6"/><path d="M0 3h14.5" fill="none" stroke="#000"/></svg>

Before

Width:  |  Height:  |  Size: 165 B

View File

@ -1 +0,0 @@
<svg width="16" height="6" xmlns="http://www.w3.org/2000/svg"><rect rx="0" ry="0" x="11" y="1" transform="rotate(45 13 3)" width="4" height="4"/><path d="M0 3h14.5" fill="none" stroke="#000"/></svg>

Before

Width:  |  Height:  |  Size: 199 B

View File

@ -1 +0,0 @@
<svg width="16" height="6" xmlns="http://www.w3.org/2000/svg"><path d="M0 3h14.5M11.7 0l1 1 1.6 2-2.6 3" fill="none" stroke="#000"/></svg>

Before

Width:  |  Height:  |  Size: 139 B

View File

@ -1 +0,0 @@
<svg viewBox="1863 1374 16 8" width="16" height="8" xmlns="http://www.w3.org/2000/svg"><path d="M1879 1374h-12s-4 0-4 4 4 4 4 4h12" fill="none" stroke="#000"/></svg>

Before

Width:  |  Height:  |  Size: 166 B

View File

@ -1 +0,0 @@
<svg width="16" height="6" xmlns="http://www.w3.org/2000/svg"><rect rx="0" ry="0" x="10" width="6" height="6" fill="#070707"/><path d="M0 3h14.5" fill="none" stroke="#000"/></svg>

Before

Width:  |  Height:  |  Size: 180 B

View File

@ -1 +0,0 @@
<svg viewBox="1863 1407 16 8" width="16" height="8" xmlns="http://www.w3.org/2000/svg"><path d="M1879 1407h-16v8h16" fill="none" stroke="#000"/></svg>

Before

Width:  |  Height:  |  Size: 151 B

View File

@ -1 +0,0 @@
<svg width="16" height="6" xmlns="http://www.w3.org/2000/svg"><path d="M0 3h14.5" fill="none" stroke="#000"/><path d="M13 0l2.9 3L13 6V0z"/></svg>

Before

Width:  |  Height:  |  Size: 147 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" stroke-linecap="round" stroke-linejoin="round">
<path d="M8 8H1m7 0a3.5 3.5 0 107 0 3.5 3.5 0 00-7 0zm1 1.25h.295m.882 1.25h.294M9 6.75h.295M10.177 8h.294m.882 1.25h.294m.882 1.25h.295m-2.647-5h.294m.882 1.25h.294M12.529 8h.295m.882 1.25H14M12.53 5.5h.294m.882 1.25H14"/>
</svg>

After

Width:  |  Height:  |  Size: 343 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" stroke-linecap="round" stroke-linejoin="round">
<path d="M7.5 8H1m8 1.25h.295m.882 1.25h.294M9 6.75h.295M10.177 8h.294m.882 1.25h.294m.882 1.25h.295m-2.647-5h.294m.882 1.25h.294M12.529 8h.295m.882 1.25H14M12.53 5.5h.294m.882 1.25H14M7.556 8.136l3.808 3.808a.192.192 0 00.272 0l3.808-3.808a.192.192 0 000-.272l-3.808-3.808a.192.192 0 00-.272 0L7.556 7.864a.192.192 0 000 .272z"/>
</svg>

After

Width:  |  Height:  |  Size: 450 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" stroke-linecap="round" stroke-linejoin="round">
<path d="M14.667 8H1.333M9.5 4l5.278 3.838a.2.2 0 010 .324L9.5 12.5"/>
</svg>

After

Width:  |  Height:  |  Size: 190 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" stroke-linecap="round" stroke-linejoin="round">
<path d="M2 5h9.823C13.578 5 15 6.343 15 8s-1.422 3-3.177 3H2"/>
</svg>

After

Width:  |  Height:  |  Size: 184 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" stroke-linecap="round" stroke-linejoin="round">
<path d="M8 8H1m8 1.25h.295m.882 1.25h.294M9 6.75h.295M10.177 8h.294m.882 1.25h.294m.882 1.25h.295m-2.647-5h.294m.882 1.25h.294M12.529 8h.295m.882 1.25H14M12.53 5.5h.294m.882 1.25H14M8.5 5h6v6h-6V5z"/>
</svg>

After

Width:  |  Height:  |  Size: 321 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" stroke-linecap="round" stroke-linejoin="round">
<path d="M2 5h13v6H2"/>
</svg>

After

Width:  |  Height:  |  Size: 143 B

View File

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" stroke-linecap="round" stroke-linejoin="round">
<path d="M9 9.25h.295m.882 1.25h.294M9 6.75h.295M10.177 8h.294m.882 1.25h.294m-1.47-3.75h.294m.882 1.25h.294M12.529 8h.295M8 8H1m13.723-.17L8.805 4.188a.2.2 0 00-.305.17v7.284a.2.2 0 00.305.17l5.918-3.642a.2.2 0 000-.34z"/>
</svg>

After

Width:  |  Height:  |  Size: 343 B

View File

@ -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;

View File

@ -159,11 +159,6 @@
}
}
}
.assets-bar .tool-window {
flex: none;
height: auto;
}
}
.empty {

View File

@ -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
[]

View File

@ -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
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

View File

@ -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?

View File

@ -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)))

View File

@ -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))))))

View File

@ -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))))))

View File

@ -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]

View File

@ -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)))))

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -85,8 +85,9 @@
.big-text {
@include inspectValue;
@include twoLineTextEllipsis;
color: var(--palette-text-color);
height: $s-16;
height: $s-28;
text-align: center;
}

View File

@ -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 {

View File

@ -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)))

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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))]])

View File

@ -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}]]]))

View File

@ -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?}]]]))

View File

@ -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}]]]))

View File

@ -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

View File

@ -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?}]]]))

View File

@ -2,7 +2,6 @@
:global(:root) {
--s-4: 0.25rem;
--layer-indentation-size: calc(var(--s-4) * 5);
}
.modal-wrapper {

View File

@ -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)}

View File

@ -62,6 +62,7 @@
flex-wrap: nowrap;
margin-top: 0;
height: 100%;
overflow: hidden;
}
.viewer-go-prev,

View File

@ -28,7 +28,6 @@
flex-direction: column;
height: 100%;
min-height: 0;
overflow: hidden;
padding: 0 $s-4 $s-8 0;
pre {

View File

@ -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]

View File

@ -78,3 +78,12 @@
}
// breakpoint 1013px
.fixed {
position: fixed;
pointer-events: none;
:global(.frame-children) g {
pointer-events: auto;
}
}

View File

@ -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";

View File

@ -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

View File

@ -8,7 +8,6 @@
.colorpicker-tooltip {
@extend .modal-background;
top: $s-100;
left: calc(10 * $s-140);
width: auto;
}

View File

@ -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])

View File

@ -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")

View File

@ -9,7 +9,6 @@
.workspace-header-left {
display: flex;
align-items: center;
height: $s-48;
padding: $s-8 $s-8 $s-4 $s-8;
}

View File

@ -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}

View File

@ -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 {

View File

@ -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}

View File

@ -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}])]))))

View File

@ -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}])])))

View File

@ -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}])])

View File

@ -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}])]))))

View File

@ -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}])]))))

View File

@ -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}])]))

View File

@ -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}])))))

View File

@ -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}])]))

View File

@ -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)

View File

@ -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;
}

View File

@ -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}]]]]]]))

View File

@ -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;
}

View File

@ -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 {

View File

@ -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}

View File

@ -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;

View File

@ -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)

View File

@ -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));

View File

@ -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))}])]))

View File

@ -64,6 +64,7 @@
font-size: $fs-10;
text-transform: uppercase;
margin-inline-start: $s-4;
color: $df-primary;
}
.attr-row {

View File

@ -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);

View File

@ -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 {

View File

@ -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

View File

@ -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)

View File

@ -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,

View File

@ -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

View File

@ -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?)

View File

@ -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)

View File

@ -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))))

View File

@ -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)