Merge pull request #6992 from penpot/niwinz-artboard-defaults

 Add defaults for artboard drawing
This commit is contained in:
Andrey Antukh 2025-07-30 13:27:54 +02:00 committed by GitHub
commit e43b6fb0b7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 303 additions and 24 deletions

View File

@ -9,6 +9,7 @@
### :heart: Community contributions (Thank you!)
### :sparkles: New features & Enhancements
- Add defaults for artboard drawing [Taiga #494](https://tree.taiga.io/project/penpot/us/494?milestone=465047)
- Continuous display of distances between elements when moving a layer with the keyboard [Taiga #1780](https://tree.taiga.io/project/penpot/issue/1780)

View File

@ -8,6 +8,7 @@
"Drawing interactions."
(:require
[app.common.data.macros :as dm]
[app.common.math :as mth]
[app.common.uuid :as uuid]
[app.main.data.workspace.common :as dwc]
[app.main.data.workspace.drawing.box :as box]
@ -100,5 +101,37 @@
:curve (curve/handle-drawing)
(box/handle-drawing type))))))
(defn change-orientation
[orientation]
(assert
(contains? #{:horizontal :vertical} orientation)
"expected valid orientation")
(ptk/reify ::change-orientation
ptk/UpdateEvent
(update [_ state]
(let [{:keys [width height]}
(get state :workspace-drawing)
width'
(if (= orientation :vertical)
(mth/min width height)
(mth/max width height))
height'
(if (= orientation :vertical)
(mth/max width height)
(mth/min width height))]
(update state :workspace-drawing assoc :width width' :height height')))))
(defn set-default-size
[width height]
(ptk/reify ::change-preset
ptk/UpdateEvent
(update [_ state]
(update state :workspace-drawing assoc :width width :height height))))

View File

@ -6,10 +6,8 @@
(ns app.main.data.workspace.drawing.common
(:require
[app.common.data.macros :as dm]
[app.common.files.helpers :as cfh]
[app.common.geom.shapes :as gsh]
[app.common.math :as mth]
[app.common.types.modifiers :as ctm]
[app.common.types.path :as path]
[app.common.types.shape :as cts]
@ -25,27 +23,35 @@
(ptk/reify ::clear-drawing
ptk/UpdateEvent
(update [_ state]
(update state :workspace-drawing dissoc :tool :object))))
(dissoc state :workspace-drawing))))
(defn handle-finish-drawing
[]
(ptk/reify ::handle-finish-drawing
ptk/WatchEvent
(watch [_ state _]
(let [tool (dm/get-in state [:workspace-drawing :tool])
shape (dm/get-in state [:workspace-drawing :object])
objects (dsh/lookup-page-objects state)
page-id (:current-page-id state)]
(let [drawing-state
(get state :workspace-drawing)
shape
(get drawing-state :object)
tool
(get drawing-state :tool)
objects
(dsh/lookup-page-objects state)
page-id
(:current-page-id state)]
(rx/concat
(when (:initialized? shape)
(let [click-draw? (:click-draw? shape)
text? (cfh/text-shape? shape)
vbox (dm/get-in state [:workspace-local :vbox])
min-side (mth/min 100
(mth/floor (dm/get-prop vbox :width))
(mth/floor (dm/get-prop vbox :height)))
width (get drawing-state :width 100)
height (get drawing-state :height 100)
shape
(cond-> shape
@ -53,14 +59,14 @@
(assoc :grow-type :fixed)
(and ^boolean click-draw? (not ^boolean text?))
(-> (assoc :width min-side)
(assoc :height min-side)
(-> (assoc :width width)
(assoc :height height)
;; NOTE: we need to recalculate the selrect and
;; points, so we assign `nil` to it
(assoc :selrect nil)
(assoc :points nil)
(cts/setup-shape)
(gsh/transform-shape (ctm/move-modifiers (- (/ min-side 2)) (- (/ min-side 2)))))
(gsh/transform-shape (ctm/move-modifiers (- (/ width 2)) (- (/ height 2)))))
(and click-draw? text?)
(-> (assoc :height 17 :width 4 :grow-type :auto-width)

View File

@ -551,9 +551,7 @@
(defn start-move-selected
"Enter mouse move mode, until mouse button is released."
([]
(start-move-selected nil false))
([] (start-move-selected nil false))
([id shift?]
(ptk/reify ::start-move-selected
ptk/WatchEvent
@ -972,8 +970,9 @@
(defn move-selected
"Move shapes a fixed increment in one direction, from a keyboard action."
[direction shift?]
(dm/assert! (contains? valid-directions direction))
(dm/assert! (boolean? shift?))
(assert (contains? valid-directions direction))
(assert (boolean? shift?))
(ptk/reify ::move-selected
ptk/WatchEvent

View File

@ -19,6 +19,7 @@
[app.main.ui.context :as ctx]
[app.main.ui.ds.layout.tab-switcher :refer [tab-switcher*]]
[app.main.ui.inspect.right-sidebar :as hrs]
[app.main.ui.workspace.sidebar.options.drawing :as drawing]
[app.main.ui.workspace.sidebar.options.menus.align :refer [align-options]]
[app.main.ui.workspace.sidebar.options.menus.bool :refer [bool-options]]
[app.main.ui.workspace.sidebar.options.menus.component :refer [component-menu]]
@ -109,11 +110,8 @@
[:& specialized-panel {:panel sp-panel}]
(d/not-empty? drawing)
[:> shape-options*
{:shape (:object drawing)
:page-id page-id
:file-id file-id
:libraries libraries}]
[:> drawing/drawing-options*
{:drawing-state drawing}]
(= 0 (count selected))
[:> page/options*]

View File

@ -0,0 +1,23 @@
;; 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.sidebar.options.drawing
(:require
[app.main.ui.workspace.sidebar.options.drawing.frame :as frame]
[rumext.v2 :as mf]))
(mf/defc drawing-options*
{::mf/wrap [#(mf/throttle % 60)]
::mf/private true}
[{:keys [drawing-state] :as props}]
(case (:tool drawing-state)
:frame
[:> frame/options* {:drawing-state drawing-state}]
nil))

View File

@ -0,0 +1,108 @@
;; 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.sidebar.options.drawing.frame
(:require-macros [app.main.style :as stl])
(:require
[app.common.data :as d]
[app.main.constants :refer [size-presets]]
[app.main.data.workspace.drawing :as dwd]
[app.main.store :as st]
[app.main.ui.components.dropdown :refer [dropdown]]
[app.main.ui.components.radio-buttons :refer [radio-button radio-buttons]]
[app.main.ui.icons :as i]
[app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]]
[rumext.v2 :as mf]))
(mf/defc options*
[{:keys [drawing-state]}]
(let [show* (mf/use-state false)
show? (deref show*)
selected-preset-name*
(mf/use-state nil)
selected-preset-name
(deref selected-preset-name*)
on-open
(mf/use-fn (fn [] (reset! show* true)))
on-close
(mf/use-fn (fn [] (reset! show* false)))
on-preset-selected
(mf/use-fn
(fn [event]
(let [target (dom/get-current-target event)
name (dom/get-data target "name")
width (-> (dom/get-data target "width")
(d/read-string))
height (-> (dom/get-data target "height")
(d/read-string))]
(reset! selected-preset-name* name)
(st/emit! (dwd/set-default-size width height)))))
orientation
(when (:width drawing-state)
(if (> (:width drawing-state) (:height drawing-state))
:horizontal
:vertical))
on-orientation-change
(mf/use-fn
(fn [orientation]
(let [orientation (keyword orientation)]
(st/emit! (dwd/change-orientation orientation)))))]
[:div {:class (stl/css :presets)}
[:div {:class (stl/css-case :presets-wrapper true
:opened show?)
:on-click on-open}
[:span {:class (stl/css :select-name)}
(or selected-preset-name
(tr "workspace.options.size-presets"))]
[:span {:class (stl/css :collapsed-icon)} i/arrow]
[:& dropdown {:show show?
:on-close on-close}
[:ul {:class (stl/css :custom-select-dropdown)}
(for [preset size-presets]
(if-not (:width preset)
[:li {:key (:name preset)
:class (stl/css-case :dropdown-element true
:disabled true)}
[:span {:class (stl/css :preset-name)} (:name preset)]]
(let [preset-match (and (= (:width preset) (:width drawing-state))
(= (:height preset) (:height drawing-state)))]
[:li {:key (:name preset)
:class (stl/css-case :dropdown-element true
:match preset-match)
:data-width (str (:width preset))
:data-height (str (:height preset))
:data-name (:name preset)
:on-click on-preset-selected}
[:div {:class (stl/css :name-wrapper)}
[:span {:class (stl/css :preset-name)} (:name preset)]
[:span {:class (stl/css :preset-size)} (:width preset) " x " (:height preset)]]
(when preset-match
[:span {:class (stl/css :check-icon)} i/tick])])))]]]
[:& radio-buttons {:selected (or (d/name orientation) "")
:on-change on-orientation-change
:name "frame-orientation"
:wide true
:class (stl/css :radio-buttons)}
[:& radio-button {:icon i/size-vertical
:value "vertical"
:id "size-vertical"}]
[:& radio-button {:icon i/size-horizontal
:value "horizontal"
:id "size-horizontal"}]]]))

View File

@ -0,0 +1,110 @@
// 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
@import "../../../../ds/_sizes.scss";
@import "refactor/common-refactor.scss";
.presets {
display: grid;
grid-template-columns: repeat(8, var(--sp-xxxl));
gap: var(--sp-xs);
grid-column: 1 / -1;
}
.presets-wrapper {
@extend .asset-element;
position: relative;
grid-column: span 6;
display: flex;
height: $s-32;
padding: $s-8;
border-radius: $br-8;
.collapsed-icon {
@include flexCenter;
cursor: pointer;
svg {
@extend .button-icon-small;
stroke: var(--icon-foreground);
transform: rotate(90deg);
}
}
&:hover {
.collapsed-icon svg {
stroke: var(--input-foreground-color-active);
}
}
}
.radio-buttons {
grid-column: span 2;
}
.select-name {
@include bodySmallTypography;
display: flex;
justify-content: flex-start;
align-items: center;
flex-grow: 1;
cursor: pointer;
}
.custom-select-dropdown {
@extend .dropdown-wrapper;
margin-top: $s-2;
max-height: 70vh;
width: $s-252;
.dropdown-element {
@extend .dropdown-element-base;
.name-wrapper {
display: flex;
gap: $s-8;
flex-grow: 1;
.preset-name {
color: var(--menu-foreground-color-rest);
}
.preset-size {
color: var(--menu-foreground-color-rest);
}
}
.check-icon {
@include flexCenter;
svg {
@extend .button-icon-small;
stroke: var(--icon-foreground);
}
}
&.disabled {
pointer-events: none;
cursor: default;
.preset-name {
color: var(--menu-foreground-color);
}
}
&.match {
.name-wrapper .preset-name {
color: var(--menu-foreground-color-hover);
}
.check-icon svg {
stroke: var(--menu-foreground-color-hover);
}
}
&:hover {
background-color: var(--menu-background-color-hover);
.name-wrapper .preset-name {
color: var(--menu-foreground-color-hover);
}
.check-icon svg {
stroke: var(--menu-foreground-color-hover);
}
}
}
}

View File

@ -338,6 +338,7 @@
:aria-label (tr "workspace.options.fit-content")
:on-pointer-down handle-fit-content
:icon "fit-content"}]])
(when (options :size)
[:div {:class (stl/css :size)}
[:div {:class (stl/css-case :width true