From 9957cba277d586bb37d4a6bedb7ac654608eb0ef Mon Sep 17 00:00:00 2001 From: elhombretecla Date: Mon, 19 Oct 2026 16:40:41 +0200 Subject: [PATCH] :sparkles: Add scale options panel to the right sidebar --- frontend/resources/images/icons/scale.svg | 3 + frontend/src/app/main/data/workspace.cljs | 1 + .../app/main/data/workspace/shortcuts.cljs | 10 +- .../app/main/data/workspace/transforms.cljs | 53 ++++ .../src/app/main/ui/ds/controls/combobox.cljs | 13 +- .../src/app/main/ui/ds/controls/combobox.mdx | 12 + .../main/ui/ds/controls/combobox.stories.jsx | 6 + .../main/ui/ds/foundations/assets/icon.cljs | 1 + .../src/app/main/ui/workspace/sidebar.cljs | 6 + .../app/main/ui/workspace/sidebar/scale.cljs | 228 ++++++++++++++++++ .../app/main/ui/workspace/sidebar/scale.scss | 99 ++++++++ frontend/translations/en.po | 48 ++++ frontend/translations/es.po | 48 ++++ 13 files changed, 523 insertions(+), 5 deletions(-) create mode 100644 frontend/resources/images/icons/scale.svg create mode 100644 frontend/src/app/main/ui/workspace/sidebar/scale.cljs create mode 100644 frontend/src/app/main/ui/workspace/sidebar/scale.scss diff --git a/frontend/resources/images/icons/scale.svg b/frontend/resources/images/icons/scale.svg new file mode 100644 index 0000000000..93b7010809 --- /dev/null +++ b/frontend/resources/images/icons/scale.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index c8e538560f..f6d2fcaab2 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -1688,6 +1688,7 @@ (dm/export dwt/update-positions) (dm/export dwt/flip-horizontal-selected) (dm/export dwt/flip-vertical-selected) +(dm/export dwt/scale-selected) (dm/export dwly/set-opacity) ;; Common diff --git a/frontend/src/app/main/data/workspace/shortcuts.cljs b/frontend/src/app/main/data/workspace/shortcuts.cljs index f91e51a5ba..42cfe0683e 100644 --- a/frontend/src/app/main/data/workspace/shortcuts.cljs +++ b/frontend/src/app/main/data/workspace/shortcuts.cljs @@ -57,6 +57,14 @@ (.preventDefault event) (st/emit! (toggle-layout-flag :display-guides))))) +(defn- escape + "Leave the scale content mode when it is active, keeping the + selection; otherwise clear the current drawing and selection." + [] + (if (contains? (deref refs/workspace-layout) :scale-text) + (st/emit! :interrupt (dw/remove-layout-flag :scale-text)) + (st/emit! :interrupt (dwdc/clear-drawing) (dw/deselect-all true)))) + (defn- emit-when-no-readonly [& events] (let [can-edit? (:can-edit (deref refs/permissions)) @@ -178,7 +186,7 @@ :command "escape" :subsections [:edit] :section [:workspace] - :fn #(st/emit! :interrupt (dwdc/clear-drawing) (dw/deselect-all true))} + :fn escape} :find {:tooltip (ds/meta "F") :command (ds/c-mod "f") :subsections [:edit] :label (fn [] (tr "shortcuts.find")) diff --git a/frontend/src/app/main/data/workspace/transforms.cljs b/frontend/src/app/main/data/workspace/transforms.cljs index 48b74d7023..b196420f0b 100644 --- a/frontend/src/app/main/data/workspace/transforms.cljs +++ b/frontend/src/app/main/data/workspace/transforms.cljs @@ -1593,6 +1593,59 @@ (rx/of (dwm/apply-wasm-modifiers modifiers {:ignore-snap-pixel true})) (rx/of (dwm/apply-modifiers {:modifiers modifiers :ignore-snap-pixel true})))))))) +;; -- Scale --------------------------------------------------------- + +(defn- anchor->point + "Return the point of `rect` that matches the `anchor`, a + `[horizontal vertical]` pair where horizontal is one of `:left`, + `:center` or `:right` and vertical one of `:top`, `:center` or + `:bottom`." + [rect [h v]] + (let [{:keys [x y width height]} rect] + (gpt/point (case h :left x :right (+ x width) (+ x (/ width 2))) + (case v :top y :bottom (+ y height) (+ y (/ height 2)))))) + +(defn scale-selected + "Scale the selected shapes and their content (strokes, texts, + shadows...) by `factor`, keeping the `anchor` point of the selection + bounds in place. Behaves like a resize in the scale content mode." + [factor anchor] + (assert (and (number? factor) (pos? factor)) "expected a positive factor") + (ptk/reify ::scale-selected + ptk/WatchEvent + (watch [_ state _] + (let [objects (dsh/lookup-page-objects state) + shapes (->> (dsh/lookup-selected state {:omit-blocked? true}) + (map (d/getf objects)) + (remove ctk/is-variant-container?))] + (if (or (empty? shapes) (mth/close? factor 1)) + (rx/empty) + (let [ids (map :id shapes) + origin (anchor->point (gsh/shapes->rect shapes) anchor) + scalev (gpt/point factor factor) + + get-modifier + (fn [shape] + (let [grow-type (get shape :grow-type) + new-grow-type (when (cfh/text-shape? shape) + (dwm/next-grow-type grow-type scalev))] + (cond-> (ctm/resize-modifiers scalev origin) + :always + (ctm/scale-content factor) + + (and (some? new-grow-type) (not= new-grow-type grow-type)) + (ctm/change-property :grow-type new-grow-type)))) + + modif-tree (dwm/build-modif-tree ids objects get-modifier)] + + (if (features/active-feature? state "render-wasm/v1") + (rx/of (dwm/apply-wasm-modifiers modif-tree + :ignore-constraints true + :ignore-snap-pixel true)) + (rx/of (dwm/apply-modifiers {:modifiers modif-tree + :ignore-constraints true + :ignore-snap-pixel true}))))))))) + (defn fit-layout-modifiers [objects frame] ;; Set temporaly the auto flag and calculate a reflow to resize and position diff --git a/frontend/src/app/main/ui/ds/controls/combobox.cljs b/frontend/src/app/main/ui/ds/controls/combobox.cljs index 2174d84a78..dcdbd5b482 100644 --- a/frontend/src/app/main/ui/ds/controls/combobox.cljs +++ b/frontend/src/app/main/ui/ds/controls/combobox.cljs @@ -12,7 +12,7 @@ [app.main.constants :refer [max-input-length]] [app.main.ui.ds.controls.select :refer [handle-focus-change]] [app.main.ui.ds.controls.shared.options-dropdown :refer [options-dropdown* schema:option]] - [app.main.ui.ds.foundations.assets.icon :refer [icon*] :as i] + [app.main.ui.ds.foundations.assets.icon :refer [icon* icon-list] :as i] [app.util.dom :as dom] [app.util.globals :as globals] [app.util.keyboard :as kbd] @@ -23,6 +23,9 @@ [rumext.v2.util :as mfu]) (:import goog.events.EventType)) +(def ^:private schema:icon + [:and :string [:fn #(contains? icon-list %)]]) + (def ^:private schema:combobox [:map [:id {:optional true} :string] @@ -30,6 +33,7 @@ [:class {:optional true} :string] [:max-length {:optional true} :int] [:placeholder {:optional true} :string] + [:icon {:optional true} [:maybe schema:icon]] [:disabled {:optional true} :boolean] [:default-selected {:optional true} :string] [:select-only {:optional true} :boolean] @@ -39,7 +43,7 @@ (mf/defc combobox* {::mf/schema schema:combobox} - [{:keys [id options class placeholder disabled has-error default-selected select-only max-length empty-to-end on-change] :rest props}] + [{:keys [id options class placeholder icon disabled has-error default-selected select-only max-length empty-to-end on-change] :rest props}] (let [;; NOTE: we use mfu/bean here for transparently handle ;; options provide as clojure data structures or javascript ;; plain objects and lists. @@ -239,8 +243,9 @@ (d/seek #(= selected-id (get % :id)) options))) icon - (when selected-option - (get selected-option :icon)) + (or (when selected-option + (get selected-option :icon)) + icon) avatar (when selected-option diff --git a/frontend/src/app/main/ui/ds/controls/combobox.mdx b/frontend/src/app/main/ui/ds/controls/combobox.mdx index 86b8e11119..de1468f054 100644 --- a/frontend/src/app/main/ui/ds/controls/combobox.mdx +++ b/frontend/src/app/main/ui/ds/controls/combobox.mdx @@ -53,6 +53,18 @@ These are available in the `app.main.ds.foundations.assets.icon` namespace. ]}] ``` +The `combobox*` itself also accepts an optional `icon`. It shows at the start of +the input when the selected option has no icon of its own, for example when +the user types a value that matches no option. + +```clj +[:> combobox* + {:icon i/scale + :default-selected "1" + :options [{ :label "1x" :id "1" } + { :label "2x" :id "2" }]}] +``` + ### Avatars diff --git a/frontend/src/app/main/ui/ds/controls/combobox.stories.jsx b/frontend/src/app/main/ui/ds/controls/combobox.stories.jsx index 63882556a9..881ed997ad 100644 --- a/frontend/src/app/main/ui/ds/controls/combobox.stories.jsx +++ b/frontend/src/app/main/ui/ds/controls/combobox.stories.jsx @@ -72,6 +72,12 @@ export const WithIcons = { }, }; +export const WithIcon = { + args: { + icon: "scale", + }, +}; + export const EmptyToEnd = { args: { emptyToEnd: true, diff --git a/frontend/src/app/main/ui/ds/foundations/assets/icon.cljs b/frontend/src/app/main/ui/ds/foundations/assets/icon.cljs index 991532c963..730ee42e37 100644 --- a/frontend/src/app/main/ui/ds/foundations/assets/icon.cljs +++ b/frontend/src/app/main/ui/ds/foundations/assets/icon.cljs @@ -235,6 +235,7 @@ (def ^:icon-id rotation "rotation") (def ^:icon-id row "row") (def ^:icon-id row-reverse "row-reverse") +(def ^:icon-id scale "scale") (def ^:icon-id search "search") (def ^:icon-id separate-nodes "separate-nodes") (def ^:icon-id settings "settings") diff --git a/frontend/src/app/main/ui/workspace/sidebar.cljs b/frontend/src/app/main/ui/workspace/sidebar.cljs index c8a0ece18e..d6075258bb 100644 --- a/frontend/src/app/main/ui/workspace/sidebar.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar.cljs @@ -37,6 +37,7 @@ [app.main.ui.workspace.sidebar.history :refer [history-toolbox*]] [app.main.ui.workspace.sidebar.layers :refer [layers-toolbox*]] [app.main.ui.workspace.sidebar.options :refer [options-toolbox*]] + [app.main.ui.workspace.sidebar.scale :refer [scale-sidebar*]] [app.main.ui.workspace.sidebar.shortcuts :refer [shortcuts-container*]] [app.main.ui.workspace.sidebar.sitemap :refer [sitemap*]] [app.main.ui.workspace.sidebar.versions :refer [versions-toolbox*]] @@ -286,6 +287,7 @@ [{:keys [layout section file-id page-id drawing-tool active-tokens] :as props}] (let [is-comments? (= drawing-tool :comments) is-history? (contains? layout :document-history) + is-scale? (contains? layout :scale-text) is-inspect? (= section :inspect) is-debug? (= section :debug) @@ -295,6 +297,7 @@ can-be-expanded? (and (not is-comments?) (not is-history?) + (not is-scale?) is-inspect? (= current-section :code)) @@ -355,6 +358,9 @@ is-history? [:> history-content* {}] + is-scale? + [:> scale-sidebar* {}] + :else (let [props (mf/spread-props props {:on-change-section on-change-section diff --git a/frontend/src/app/main/ui/workspace/sidebar/scale.cljs b/frontend/src/app/main/ui/workspace/sidebar/scale.cljs new file mode 100644 index 0000000000..2b676cd5f1 --- /dev/null +++ b/frontend/src/app/main/ui/workspace/sidebar/scale.cljs @@ -0,0 +1,228 @@ +;; 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 SUBSIDIARY SL + +(ns app.main.ui.workspace.sidebar.scale + "Right sidebar panel of the scale content mode (K shortcut)." + (:require-macros [app.main.style :as stl]) + (:require + [app.common.data :as d] + [app.common.geom.shapes :as gsh] + [app.common.math :as mth] + [app.main.data.workspace :as dw] + [app.main.refs :as refs] + [app.main.store :as st] + [app.main.ui.ds.controls.combobox :refer [combobox*]] + [app.main.ui.ds.controls.numeric-input :refer [numeric-input*]] + [app.main.ui.ds.controls.utilities.label :refer [label*]] + [app.main.ui.ds.foundations.assets.icon :as i] + [app.main.ui.ds.product.panel-title :refer [panel-title*]] + [app.util.dom :as dom] + [app.util.i18n :refer [tr]] + [cuerdas.core :as str] + [rumext.v2 :as mf])) + +(def ^:private scale-presets + [0.5 0.75 1 1.5 2 3 4]) + +(def ^:private anchors + [[:left :top] [:center :top] [:right :top] + [:left :center] [:center :center] [:right :center] + [:left :bottom] [:center :bottom] [:right :bottom]]) + +(def ^:private indexed-anchors + (d/enumerate anchors)) + +(def ^:private default-anchor + [:center :center]) + +(defn- factor->id + [factor] + (str (mth/precision factor 2))) + +(defn- parse-factor + "Parse a scale typed by the user, like `2`, `1.5x` or `0,5x`" + [value] + (let [factor (-> (str value) + (str/trim) + (str/rtrim "xX") + (str/replace "," ".") + (d/parse-double))] + (when (and (some? factor) (pos? factor)) + factor))) + +(defn- anchor-label + [anchor] + (case anchor + [:left :top] (tr "workspace.scale.anchor.top-left") + [:center :top] (tr "workspace.scale.anchor.top-center") + [:right :top] (tr "workspace.scale.anchor.top-right") + [:left :center] (tr "workspace.scale.anchor.center-left") + [:center :center] (tr "workspace.scale.anchor.center-center") + [:right :center] (tr "workspace.scale.anchor.center-right") + [:left :bottom] (tr "workspace.scale.anchor.bottom-left") + [:center :bottom] (tr "workspace.scale.anchor.bottom-center") + [:right :bottom] (tr "workspace.scale.anchor.bottom-right"))) + +(mf/defc anchor-point-selector* + {::mf/private true} + [{:keys [value disabled on-change aria-labelledby]}] + (let [on-click + (mf/use-fn + (mf/deps on-change) + (fn [event] + (let [index (-> (dom/get-current-target event) + (dom/get-data "index") + (d/parse-integer))] + (on-change (nth anchors index)))))] + + [:div {:class (stl/css :anchor-grid) + :role "radiogroup" + :aria-labelledby aria-labelledby} + (for [[index anchor] indexed-anchors] + (let [selected? (= anchor value)] + [:button {:key index + :type "button" + :role "radio" + :class (stl/css-case :anchor-point true + :center-area (= anchor default-anchor) + :selected selected?) + :aria-checked selected? + :aria-label (anchor-label anchor) + :disabled disabled + :data-index index + :on-click on-click} + [:span {:class (stl/css :anchor-mark)}]]))])) + +(mf/defc scale-sidebar* + [] + (let [selected (mf/deref refs/selected-shapes) + shapes-ref (mf/with-memo [selected] + (refs/objects-by-id selected)) + shapes (mf/deref shapes-ref) + + rect (mf/with-memo [shapes] + (when (seq shapes) + (gsh/shapes->rect shapes))) + + width (get rect :width) + height (get rect :height) + size (when rect (max width height)) + disabled (or (nil? size) (mth/almost-zero? size)) + + ;; The scale factor is relative to the size of the selection + ;; when it was selected, so it is only reset when the + ;; selection changes. + base-size (mf/with-memo [selected] + size) + + factor (if (and (not disabled) (some? base-size) (pos? base-size)) + (/ size base-size) + 1) + + factor-id (factor->id factor) + + anchor* (mf/use-state default-anchor) + anchor (deref anchor*) + + width-id (mf/use-id) + height-id (mf/use-id) + scale-id (mf/use-id) + anchor-id (mf/use-id) + + scale-options + (mf/with-memo [] + (mapv (fn [factor] + {:id (factor->id factor) + :label (str (factor->id factor) "x")}) + scale-presets)) + + default-selected + (if (some #(= factor-id (:id %)) scale-options) + factor-id + (str factor-id "x")) + + scale-to + (mf/use-fn + (mf/deps size anchor) + (fn [new-size] + (when (and (some? size) (pos? size) (pos? new-size)) + (st/emit! (dw/scale-selected (/ new-size size) anchor))))) + + on-width-change + (mf/use-fn + (mf/deps scale-to width size) + (fn [value] + (when (and (number? value) (pos? width)) + (scale-to (* size (/ value width)))))) + + on-height-change + (mf/use-fn + (mf/deps scale-to height size) + (fn [value] + (when (and (number? value) (pos? height)) + (scale-to (* size (/ value height)))))) + + on-scale-change + (mf/use-fn + (mf/deps scale-to base-size factor-id) + (fn [value] + ;; The combobox also reports its value on blur, skip it when + ;; it is the same rounded factor we are already showing. + (when-let [factor (parse-factor value)] + (when (not= factor-id (factor->id factor)) + (scale-to (* base-size factor)))))) + + on-anchor-change + (mf/use-fn + #(reset! anchor* %)) + + on-close + (mf/use-fn + #(st/emit! (dw/remove-layout-flag :scale-text)))] + + [:div {:class (stl/css :scale-sidebar)} + [:> panel-title* {:class (stl/css :scale-title) + :text (tr "workspace.scale.title") + :on-close on-close}] + + [:div {:class (stl/css :scale-content)} + [:div {:class (stl/css :field)} + [:> label* {:for width-id} (tr "workspace.options.width")] + [:> numeric-input* {:id width-id + :icon i/character-w + :min 0.01 + :disabled disabled + :placeholder "--" + :value width + :on-change on-width-change}]] + + [:div {:class (stl/css :field)} + [:> label* {:for height-id} (tr "workspace.options.height")] + [:> numeric-input* {:id height-id + :icon i/character-h + :min 0.01 + :disabled disabled + :placeholder "--" + :value height + :on-change on-height-change}]] + + [:div {:class (stl/css :field)} + [:> label* {:for scale-id} (tr "workspace.scale.scale")] + [:> combobox* {:id scale-id + :icon i/scale + :disabled disabled + :options scale-options + :default-selected default-selected + :on-change on-scale-change}]] + + [:div {:class (stl/css :field)} + [:span {:id anchor-id + :class (stl/css :anchor-title)} + (tr "workspace.scale.anchor-point")] + [:> anchor-point-selector* {:value anchor + :disabled disabled + :aria-labelledby anchor-id + :on-change on-anchor-change}]]]])) diff --git a/frontend/src/app/main/ui/workspace/sidebar/scale.scss b/frontend/src/app/main/ui/workspace/sidebar/scale.scss new file mode 100644 index 0000000000..a3199c8e03 --- /dev/null +++ b/frontend/src/app/main/ui/workspace/sidebar/scale.scss @@ -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 SUBSIDIARY SL + +@use "ds/_borders.scss" as *; +@use "ds/_sizes.scss" as *; +@use "ds/_utils.scss" as *; +@use "ds/typography.scss" as t; + +.scale-sidebar { + display: flex; + flex-direction: column; + background-color: var(--panel-background-color); +} + +.scale-title { + margin: var(--sp-s) var(--sp-s) 0; +} + +.scale-content { + display: grid; + grid-template-columns: 1fr 1fr; + gap: var(--sp-l) var(--sp-s); + padding: var(--sp-l) var(--sp-m); +} + +.field { + display: flex; + flex-direction: column; + gap: var(--sp-xs); + min-inline-size: 0; +} + +.anchor-title { + @include t.use-typography("body-small"); + + color: var(--color-foreground-primary); +} + +.anchor-grid { + --anchor-side-size: #{$sz-24}; + --anchor-center-size: #{px2rem(60)}; + + display: grid; + grid-template-columns: var(--anchor-side-size) var(--anchor-center-size) var(--anchor-side-size); + grid-template-rows: var(--anchor-side-size) var(--anchor-center-size) var(--anchor-side-size); + inline-size: fit-content; + block-size: fit-content; + border-radius: $br-8; + background-color: var(--color-background-tertiary); +} + +// The unselected marks are 2/3 of the selected one, ring included +$anchor-selected-size: px2rem(10); + +.anchor-point { + --anchor-mark-size: calc((#{$anchor-selected-size} + 2 * #{$b-2}) * 2 / 3); + --anchor-mark-color: var(--color-foreground-secondary); + --anchor-mark-outline-color: transparent; + + all: unset; + display: grid; + place-items: center; + cursor: pointer; + + &:hover:not(:disabled) { + --anchor-mark-color: var(--color-foreground-primary); + } + + &:focus-visible { + outline: $b-1 solid var(--color-accent-primary); + border-radius: $br-4; + } + + &:disabled { + cursor: default; + } + + &.selected { + --anchor-mark-size: #{$anchor-selected-size}; + --anchor-mark-color: var(--color-accent-primary); + --anchor-mark-outline-color: var(--color-accent-primary-muted); + } +} + +.center-area { + border-radius: $br-8; + background-color: var(--color-background-primary); +} + +.anchor-mark { + inline-size: var(--anchor-mark-size); + block-size: var(--anchor-mark-size); + border-radius: $br-4; + background-color: var(--anchor-mark-color); + outline: $b-2 solid var(--anchor-mark-outline-color); +} diff --git a/frontend/translations/en.po b/frontend/translations/en.po index a735f09beb..cce3e35476 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -8340,6 +8340,54 @@ msgstr "'%s' PLUGIN IS INSTALLED FOR YOUR USER!" msgid "workspace.plugins.try-out.try" msgstr "TRY PLUGIN" +#: src/app/main/ui/workspace/sidebar/scale.cljs +msgid "workspace.scale.anchor-point" +msgstr "Anchor point" + +#: src/app/main/ui/workspace/sidebar/scale.cljs +msgid "workspace.scale.anchor.bottom-center" +msgstr "Bottom center" + +#: src/app/main/ui/workspace/sidebar/scale.cljs +msgid "workspace.scale.anchor.bottom-left" +msgstr "Bottom left" + +#: src/app/main/ui/workspace/sidebar/scale.cljs +msgid "workspace.scale.anchor.bottom-right" +msgstr "Bottom right" + +#: src/app/main/ui/workspace/sidebar/scale.cljs +msgid "workspace.scale.anchor.center-center" +msgstr "Center" + +#: src/app/main/ui/workspace/sidebar/scale.cljs +msgid "workspace.scale.anchor.center-left" +msgstr "Center left" + +#: src/app/main/ui/workspace/sidebar/scale.cljs +msgid "workspace.scale.anchor.center-right" +msgstr "Center right" + +#: src/app/main/ui/workspace/sidebar/scale.cljs +msgid "workspace.scale.anchor.top-center" +msgstr "Top center" + +#: src/app/main/ui/workspace/sidebar/scale.cljs +msgid "workspace.scale.anchor.top-left" +msgstr "Top left" + +#: src/app/main/ui/workspace/sidebar/scale.cljs +msgid "workspace.scale.anchor.top-right" +msgstr "Top right" + +#: src/app/main/ui/workspace/sidebar/scale.cljs +msgid "workspace.scale.scale" +msgstr "Scale" + +#: src/app/main/ui/workspace/sidebar/scale.cljs +msgid "workspace.scale.title" +msgstr "Scale" + #: src/app/main/ui/workspace/context_menu.cljs:659 msgid "workspace.shape.menu.add-flex" msgstr "Add flex layout" diff --git a/frontend/translations/es.po b/frontend/translations/es.po index 6cc8b78b9d..a13c9c3b07 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -8431,6 +8431,54 @@ msgstr "¡LA EXTENSIÓN '%s' HA SIDO INSTALADA PARA TU USUARIO!" msgid "workspace.plugins.try-out.try" msgstr "PROBAR PLUGIN" +#: src/app/main/ui/workspace/sidebar/scale.cljs +msgid "workspace.scale.anchor-point" +msgstr "Punto de anclaje" + +#: src/app/main/ui/workspace/sidebar/scale.cljs +msgid "workspace.scale.anchor.bottom-center" +msgstr "Abajo en el centro" + +#: src/app/main/ui/workspace/sidebar/scale.cljs +msgid "workspace.scale.anchor.bottom-left" +msgstr "Abajo a la izquierda" + +#: src/app/main/ui/workspace/sidebar/scale.cljs +msgid "workspace.scale.anchor.bottom-right" +msgstr "Abajo a la derecha" + +#: src/app/main/ui/workspace/sidebar/scale.cljs +msgid "workspace.scale.anchor.center-center" +msgstr "Centro" + +#: src/app/main/ui/workspace/sidebar/scale.cljs +msgid "workspace.scale.anchor.center-left" +msgstr "Centro a la izquierda" + +#: src/app/main/ui/workspace/sidebar/scale.cljs +msgid "workspace.scale.anchor.center-right" +msgstr "Centro a la derecha" + +#: src/app/main/ui/workspace/sidebar/scale.cljs +msgid "workspace.scale.anchor.top-center" +msgstr "Arriba en el centro" + +#: src/app/main/ui/workspace/sidebar/scale.cljs +msgid "workspace.scale.anchor.top-left" +msgstr "Arriba a la izquierda" + +#: src/app/main/ui/workspace/sidebar/scale.cljs +msgid "workspace.scale.anchor.top-right" +msgstr "Arriba a la derecha" + +#: src/app/main/ui/workspace/sidebar/scale.cljs +msgid "workspace.scale.scale" +msgstr "Escala" + +#: src/app/main/ui/workspace/sidebar/scale.cljs +msgid "workspace.scale.title" +msgstr "Escalado" + #: src/app/main/ui/workspace/context_menu.cljs:659 msgid "workspace.shape.menu.add-flex" msgstr "Añadir flex layout"