0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-03-13 16:21:57 -05:00

Shortcuts for paths

This commit is contained in:
alonso.torres 2021-04-22 14:06:11 +02:00
parent 6331dff484
commit 65ad46ab38
15 changed files with 320 additions and 116 deletions

View file

@ -305,10 +305,9 @@
}
&.is-disabled {
opacity: 0.3;
&:hover svg {
fill: initial;
cursor: initial;
svg {
fill: $color-gray-20;
}
}

View file

@ -6,15 +6,13 @@
(ns app.main.data.shortcuts
(:require
[app.main.data.workspace.colors :as mdc]
[app.main.data.workspace.transforms :as dwt]
[app.main.store :as st]
[app.util.dom :as dom]
[potok.core :as ptk]
[beicon.core :as rx]
[app.config :as cfg])
["mousetrap" :as mousetrap]
[app.config :as cfg]
[app.util.logging :as log])
(:refer-clojure :exclude [meta]))
(log/set-level! :warn)
(def mac-command "\u2318")
(def mac-option "\u2325")
(def mac-delete "\u232B")
@ -46,20 +44,41 @@
[shortcut]
(c-mod (a-mod shortcut)))
(defn bind-shortcuts [shortcuts bind-fn cb-fn]
(doseq [[key {:keys [command disabled fn type]}] shortcuts]
(when-not disabled
(if (vector? command)
(doseq [cmd (seq command)]
(bind-fn cmd (cb-fn key fn) type))
(bind-fn command (cb-fn key fn) type)))))
(defn bind-shortcuts
([shortcuts-config]
(bind-shortcuts
shortcuts-config
mousetrap/bind
(fn [key cb]
(fn [event]
(log/debug :msg (str "Shortcut" key))
(.preventDefault event)
(cb event)))))
([shortcuts-config bind-fn cb-fn]
(doseq [[key {:keys [command disabled fn type]}] shortcuts-config]
(when-not disabled
(if (vector? command)
(doseq [cmd (seq command)]
(bind-fn cmd (cb-fn key fn) type))
(bind-fn command (cb-fn key fn) type))))))
(defn remove-shortcuts
[]
(mousetrap/reset))
(defn meta [key]
(str
(if (cfg/check-platform? :macos)
mac-command
"Ctrl+")
key))
;; If the key is "+" we need to surround with quotes
;; otherwise will not be very readable
(let [key (if (and (not (cfg/check-platform? :macos))
(= key "+"))
"\"+\""
key)]
(str
(if (cfg/check-platform? :macos)
mac-command
"Ctrl+")
key)))
(defn shift [key]
(str

View file

@ -10,7 +10,8 @@
[app.main.data.workspace.path.drawing :as drawing]
[app.main.data.workspace.path.edition :as edition]
[app.main.data.workspace.path.selection :as selection]
[app.main.data.workspace.path.tools :as tools]))
[app.main.data.workspace.path.tools :as tools]
[app.main.data.workspace.path.undo :as undo]))
;; Drawing
(d/export drawing/handle-new-shape)
@ -42,3 +43,7 @@
(d/export tools/separate-nodes)
(d/export tools/toggle-snap)
;; Undo/redo
(d/export undo/undo-path)
(d/export undo/redo-path)
(d/export undo/merge-head)

View file

@ -113,7 +113,8 @@
(let [id (st/get-path-id state)
handler (get-in state [:workspace-local :edit-path id :prev-handler])]
;; Update the preview because can be outdated after the dragging
(rx/of (preview-next-point handler))))))
(rx/of (preview-next-point handler)
(undo/merge-head))))))
(declare close-path-drag-end)

View file

@ -0,0 +1,94 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) UXBOX Labs SL
(ns app.main.data.workspace.path.shortcuts
(:require
[app.main.data.shortcuts :as ds]
[app.main.data.workspace :as dw]
[app.main.data.workspace.path :as drp]
[app.main.store :as st]
[beicon.core :as rx]
[potok.core :as ptk]))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Shortcuts
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Shortcuts format https://github.com/ccampbell/mousetrap
(defn esc-pressed []
(ptk/reify :esc-pressed
ptk/WatchEvent
(watch [_ state stream]
;; Not interrupt when we're editing a path
(let [edition-id (or (get-in state [:workspace-drawing :object :id])
(get-in state [:workspace-local :edition]))
path-edit-mode (get-in state [:workspace-local :edit-path edition-id :edit-mode])]
(if-not (= :draw path-edit-mode)
(rx/of :interrupt (dw/deselect-all true))
(rx/empty))))))
(def shortcuts
{:move-nodes {:tooltip "V"
:command "v"
:fn #(st/emit! (drp/change-edit-mode :move))}
:draw-nodes {:tooltip "P"
:command "p"
:fn #(st/emit! (drp/change-edit-mode :draw))}
:add-node {:tooltip (ds/meta "+")
:command (ds/c-mod "+")
:fn #(st/emit! (drp/add-node))}
:delete-node {:tooltip (ds/supr)
:command ["del" "backspace"]
:fn #(st/emit! (drp/remove-node))}
:merge-nodes {:tooltip (ds/meta "J")
:command (ds/c-mod "j")
:fn #(st/emit! (drp/merge-nodes))}
:join-nodes {:tooltip (ds/meta-shift "J")
:command (ds/c-mod "shift+j")
:fn #(st/emit! (drp/join-nodes))}
:separate-nodes {:tooltip (ds/meta "K")
:command (ds/c-mod "k")
:fn #(st/emit! (drp/separate-nodes))}
:make-corner {:tooltip (ds/meta "B")
:command (ds/c-mod "b")
:fn #(st/emit! (drp/make-corner))}
:make-curve {:tooltip (ds/meta-shift "B")
:command (ds/c-mod "shift+b")
:fn #(st/emit! (drp/make-curve))}
:snap-nodes {:tooltip (ds/meta "'")
:command (ds/c-mod "'")
:fn #(st/emit! (drp/toggle-snap))}
:escape {:tooltip (ds/esc)
:command "escape"
:fn #(st/emit! (esc-pressed))}
:start-editing {:tooltip (ds/enter)
:command "enter"
:fn #(st/emit! (dw/start-editing-selected))}
:undo {:tooltip (ds/meta "Z")
:command (ds/c-mod "z")
:fn #(st/emit! (drp/undo-path))}
:redo {:tooltip (ds/meta "Y")
:command [(ds/c-mod "shift+z") (ds/c-mod "y")]
:fn #(st/emit! (drp/redo-path))}
})
(defn get-tooltip [shortcut]
(assert (contains? shortcuts shortcut) (str shortcut))
(get-in shortcuts [shortcut :tooltip]))

View file

@ -72,6 +72,14 @@
(->> ms/mouse-position
(rx/map check-path-snap))))
(defn get-angle [node handler opposite]
(when (and (some? node) (some? handler) (some? opposite))
(let [v1 (gpt/to-vec node opposite)
v2 (gpt/to-vec node handler)
rot-angle (gpt/angle-with-other v1 v2)
rot-sign (gpt/angle-sign v1 v2)]
[rot-angle rot-sign])))
(defn move-handler-stream
[snap-toggled start-point node handler opposite points]
@ -79,8 +87,7 @@
ranges (snap/create-ranges points)
d-pos (/ snap/snap-path-accuracy zoom)
initial-angle (gpt/angle-with-other (gpt/to-vec node handler)
(gpt/to-vec node opposite))
[initial-angle] (get-angle node handler opposite)
check-path-snap
(fn [position]
@ -88,14 +95,11 @@
(let [delta (gpt/subtract position start-point)
handler (gpt/add handler delta)
v1 (gpt/to-vec node opposite)
v2 (gpt/to-vec node handler)
rot-angle (gpt/angle-with-other v1 v2)
rot-sign (gpt/angle-sign v1 v2)
[rot-angle rot-sign] (get-angle node handler opposite)
snap-opposite-angle?
(and (or (:alt? position) (> (- 180 initial-angle) 0.1))
(and (some? rot-angle)
(or (:alt? position) (> (- 180 initial-angle) 0.1))
(<= (- 180 rot-angle) 5))]
(cond

View file

@ -48,8 +48,8 @@
:prev-handler prev-handler
:old-content old-content))))
(defn undo []
(ptk/reify ::undo
(defn undo-path []
(ptk/reify ::undo-path
ptk/UpdateEvent
(update [_ state]
(let [id (st/get-path-id state)
@ -65,10 +65,10 @@
ptk/WatchEvent
(watch [_ state stream]
(rx/of (changes/save-path-content)))))
(rx/of (changes/save-path-content {:preserve-move-to true})))))
(defn redo []
(ptk/reify ::redo
(defn redo-path []
(ptk/reify ::redo-path
ptk/UpdateEvent
(update [_ state]
(let [id (st/get-path-id state)
@ -85,6 +85,23 @@
(watch [_ state stream]
(rx/of (changes/save-path-content)))))
(defn merge-head
"Joins the head with the previous undo in one. This is done so when the user changes a
node handlers after adding it the undo merges both in one operation only"
[]
(ptk/reify ::add-undo-entry
ptk/UpdateEvent
(update [_ state]
(let [id (st/get-path-id state)
entry (make-entry state)
stack (get-in state [:workspace-local :edit-path id :undo-stack])
head (u/peek stack)
stack (-> stack (u/undo) (u/fixup head))]
(-> state
(d/assoc-in-when
[:workspace-local :edit-path id :undo-stack]
stack))))))
(defn add-undo-entry []
(ptk/reify ::add-undo-entry
ptk/UpdateEvent
@ -136,20 +153,10 @@
(rx/filter stop-undo?)
(rx/take 1))]
(rx/concat
(->> (rx/merge
(->> (rx/from-atom path-content-ref {:emit-current-value? true})
(rx/filter (comp not nil?))
(rx/map #(add-undo-entry)))
(->> stream
(rx/filter undo-event?)
(rx/map #(undo)))
(->> stream
(rx/filter redo-event?)
(rx/map #(redo))))
(rx/take-until stop-undo-stream))
(->> (rx/from-atom path-content-ref {:emit-current-value? true})
(rx/take-until stop-undo-stream)
(rx/filter (comp not nil?))
(rx/map #(add-undo-entry)))
(rx/of (end-path-undo))))))))))

View file

@ -6,10 +6,9 @@
(ns app.main.data.workspace.shortcuts
(:require
[app.config :as cfg]
[app.main.data.workspace.colors :as mdc]
[app.main.data.shortcuts :as ds]
[app.main.data.workspace :as dw]
[app.main.data.workspace.colors :as mdc]
[app.main.data.workspace.common :as dwc]
[app.main.data.workspace.drawing :as dwd]
[app.main.data.workspace.libraries :as dwl]
@ -17,28 +16,13 @@
[app.main.data.workspace.transforms :as dwt]
[app.main.store :as st]
[app.util.dom :as dom]
[beicon.core :as rx]
[potok.core :as ptk]))
;; \u2318P
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Shortcuts
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Shortcuts impl https://github.com/ccampbell/mousetrap
(defn esc-pressed []
(ptk/reify :esc-pressed
ptk/WatchEvent
(watch [_ state stream]
;; Not interrupt when we're editing a path
(let [edition-id (or (get-in state [:workspace-drawing :object :id])
(get-in state [:workspace-local :edition]))
path-edit-mode (get-in state [:workspace-local :edit-path edition-id :edit-mode])]
(if-not (= :draw path-edit-mode)
(rx/of :interrupt (dw/deselect-all true))
(rx/empty))))))
;; Shortcuts format https://github.com/ccampbell/mousetrap
(def shortcuts
{:toggle-layers {:tooltip (ds/alt "L")
@ -252,7 +236,7 @@
:escape {:tooltip (ds/esc)
:command "escape"
:fn #(st/emit! (esc-pressed))}
:fn #(st/emit! :interrupt (dw/deselect-all true))}
:start-editing {:tooltip (ds/enter)
:command "enter"

View file

@ -7,9 +7,8 @@
(ns app.main.ui.hooks
"A collection of general purpose react hooks."
(:require
["mousetrap" :as mousetrap]
[app.common.spec :as us]
[app.main.data.shortcuts :refer [bind-shortcuts]]
[app.main.data.shortcuts :as dsc]
[app.util.dom :as dom]
[app.util.object :as obj]
[app.util.dom.dnd :as dnd]
@ -39,16 +38,8 @@
[shortcuts]
(mf/use-effect
(fn []
(bind-shortcuts
shortcuts
mousetrap/bind
(fn [key cb]
(fn [event]
(log/debug :msg (str "Shortcut" key))
(.preventDefault event)
(cb event))))
(fn [] (mousetrap/reset))))
nil)
(dsc/bind-shortcuts shortcuts)
(fn [] (dsc/remove-shortcuts)))))
(defn invisible-image
[]

View file

@ -11,7 +11,6 @@
[app.main.data.history :as udh]
[app.main.data.messages :as dm]
[app.main.data.workspace :as dw]
[app.main.data.workspace.shortcuts :as sc]
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.streams :as ms]
@ -21,13 +20,13 @@
[app.main.ui.workspace.colorpalette :refer [colorpalette]]
[app.main.ui.workspace.colorpicker]
[app.main.ui.workspace.context-menu :refer [context-menu]]
[app.main.ui.workspace.coordinates :as coordinates]
[app.main.ui.workspace.header :refer [header]]
[app.main.ui.workspace.left-toolbar :refer [left-toolbar]]
[app.main.ui.workspace.libraries]
[app.main.ui.workspace.rules :refer [horizontal-rule vertical-rule]]
[app.main.ui.workspace.sidebar :refer [left-sidebar right-sidebar]]
[app.main.ui.workspace.viewport :refer [viewport]]
[app.main.ui.workspace.coordinates :as coordinates]
[app.util.dom :as dom]
[app.util.i18n :as i18n :refer [tr]]
[app.util.keyboard :as kbd]
@ -114,30 +113,29 @@
(mf/defc workspace
{::mf/wrap [mf/memo]}
[{:keys [project-id file-id page-id layout-name] :as props}]
(mf/use-effect
(mf/deps layout-name)
#(st/emit! (dw/initialize-layout layout-name)))
(mf/use-effect
(mf/deps project-id file-id)
(fn []
(st/emit! (dw/initialize-file project-id file-id))
(st/emitf (dw/finalize-file project-id file-id))))
(mf/use-effect
(fn []
;; Close any non-modal dialog that may be still open
(st/emitf dm/hide)))
(hooks/use-shortcuts sc/shortcuts)
(let [file (mf/deref refs/workspace-file)
project (mf/deref refs/workspace-project)
layout (mf/deref refs/workspace-layout)]
(mf/use-effect
(mf/deps file)
#(dom/set-html-title (tr "title.workspace" (:name file))))
(mf/deps layout-name)
#(st/emit! (dw/initialize-layout layout-name)))
(mf/use-effect
(mf/deps project-id file-id)
(fn []
(st/emit! (dw/initialize-file project-id file-id))
(st/emitf (dw/finalize-file project-id file-id))))
(mf/use-effect
(fn []
;; Close any non-modal dialog that may be still open
(st/emitf dm/hide)))
(mf/use-effect
(mf/deps file)
#(dom/set-html-title (tr "title.workspace" (:name file))))
[:& (mf/provider ctx/current-file-id) {:value (:id file)}
[:& (mf/provider ctx/current-team-id) {:value (:team-id project)}

View file

@ -143,6 +143,7 @@
(hooks/setup-keyboard alt? ctrl?)
(hooks/setup-hover-shapes page-id move-stream selected objects transform selected ctrl? hover hover-ids)
(hooks/setup-viewport-modifiers modifiers selected objects render-ref)
(hooks/setup-shortcuts path-editing? drawing-path?)
[:div.viewport
[:div.viewport-overlays

View file

@ -9,7 +9,10 @@
[app.common.data :as d]
[app.common.geom.shapes :as gsh]
[app.common.pages :as cp]
[app.main.data.shortcuts :as dsc]
[app.main.data.workspace :as dw]
[app.main.data.workspace.path.shortcuts :as psc]
[app.main.data.workspace.shortcuts :as wsc]
[app.main.store :as st]
[app.main.streams :as ms]
[app.main.ui.hooks :as hooks]
@ -148,3 +151,15 @@
(if modifiers
(utils/update-transform render-node roots modifiers)
(utils/remove-transform render-node roots))))))
(defn setup-shortcuts [path-editing? drawing-path?]
(mf/use-effect
(mf/deps path-editing? drawing-path?)
(fn []
(cond
(or drawing-path? path-editing?)
(dsc/bind-shortcuts psc/shortcuts)
:else
(dsc/bind-shortcuts wsc/shortcuts))
dsc/remove-shortcuts)))

View file

@ -8,10 +8,12 @@
(:require
[app.main.data.workspace.path :as drp]
[app.main.data.workspace.path.helpers :as wph]
[app.main.data.workspace.path.shortcuts :as sc]
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.icons :as i]
[app.main.ui.workspace.shapes.path.common :as pc]
[app.util.i18n :as i18n :refer [tr]]
[app.util.path.tools :as upt]
[rumext.alpha :as mf]))
@ -106,65 +108,75 @@
[:div.viewport-actions-group
;; Draw Mode
[:div.viewport-actions-entry
[:div.viewport-actions-entry.tooltip.tooltip-bottom
{:class (when (= edit-mode :draw) "is-toggled")
:alt (tr "workspace.path.actions.move-nodes" (sc/get-tooltip :move-nodes))
:on-click on-select-draw-mode}
i/pen]
;; Edit mode
[:div.viewport-actions-entry
[:div.viewport-actions-entry.tooltip.tooltip-bottom
{:class (when (= edit-mode :move) "is-toggled")
:alt (tr "workspace.path.actions.draw-nodes" (sc/get-tooltip :draw-nodes))
:on-click on-select-edit-mode}
i/pointer-inner]]
[:div.viewport-actions-group
;; Add Node
[:div.viewport-actions-entry
[:div.viewport-actions-entry.tooltip.tooltip-bottom
{:class (when-not (:add-node enabled-buttons) "is-disabled")
:alt (tr "workspace.path.actions.add-node" (sc/get-tooltip :add-node))
:on-click on-add-node}
i/nodes-add]
;; Remove node
[:div.viewport-actions-entry
[:div.viewport-actions-entry.tooltip.tooltip-bottom
{:class (when-not (:remove-node enabled-buttons) "is-disabled")
:alt (tr "workspace.path.actions.delete-node" (sc/get-tooltip :delete-node))
:on-click on-remove-node}
i/nodes-remove]]
[:div.viewport-actions-group
;; Merge Nodes
[:div.viewport-actions-entry
[:div.viewport-actions-entry.tooltip.tooltip-bottom
{:class (when-not (:merge-nodes enabled-buttons) "is-disabled")
:alt (tr "workspace.path.actions.merge-nodes" (sc/get-tooltip :merge-nodes))
:on-click on-merge-nodes}
i/nodes-merge]
;; Join Nodes
[:div.viewport-actions-entry
[:div.viewport-actions-entry.tooltip.tooltip-bottom
{:class (when-not (:join-nodes enabled-buttons) "is-disabled")
:alt (tr "workspace.path.actions.join-nodes" (sc/get-tooltip :join-nodes))
:on-click on-join-nodes}
i/nodes-join]
;; Separate Nodes
[:div.viewport-actions-entry
[:div.viewport-actions-entry.tooltip.tooltip-bottom
{:class (when-not (:separate-nodes enabled-buttons) "is-disabled")
:alt (tr "workspace.path.actions.separate-nodes" (sc/get-tooltip :separate-nodes))
:on-click on-separate-nodes}
i/nodes-separate]]
;; Make Corner
[:div.viewport-actions-group
[:div.viewport-actions-entry
[:div.viewport-actions-entry.tooltip.tooltip-bottom
{:class (when-not (:make-corner enabled-buttons) "is-disabled")
:alt (tr "workspace.path.actions.make-corner" (sc/get-tooltip :make-corner))
:on-click on-make-corner}
i/nodes-corner]
;; Make Curve
[:div.viewport-actions-entry
[:div.viewport-actions-entry.tooltip.tooltip-bottom
{:class (when-not (:make-curve enabled-buttons) "is-disabled")
:alt (tr "workspace.path.actions.make-curve" (sc/get-tooltip :make-curve))
:on-click on-make-curve}
i/nodes-curve]]
;; Toggle snap
[:div.viewport-actions-group
[:div.viewport-actions-entry
[:div.viewport-actions-entry.tooltip.tooltip-bottom
{:class (when snap-toggled "is-toggled")
:alt (tr "workspace.path.actions.snap-nodes" (sc/get-tooltip :snap-nodes))
:on-click on-toggle-snap}
i/nodes-snap]]]))

View file

@ -1,11 +1,25 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR Free Software Foundation, Inc.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Language: en\n"
"Project-Id-Version: PACKAGE VERSION\n"
"PO-Revision-Date: 2021-04-22 13:43+0200\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Type: text/plain; charset=iso-8859-1\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
# ~ msgid ""
# ~ msgstr ""
# ~ "Language: en\n"
# ~ "MIME-Version: 1.0\n"
# ~ "Content-Type: text/plain; charset=utf-8\n"
# ~ "Content-Transfer-Encoding: 8bit\n"
# ~ "Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: src/app/main/ui/auth/register.cljs
msgid "auth.already-have-account"
msgstr "Already have an account?"
@ -2169,6 +2183,36 @@ msgstr "Vertical align"
msgid "workspace.options.use-play-button"
msgstr "Use the play button at the header to run the prototype view."
msgid "workspace.path.actions.add-node"
msgstr "Add node (%s)"
msgid "workspace.path.actions.delete-node"
msgstr "Delete node (%s)"
msgid "workspace.path.actions.draw-nodes"
msgstr "Draw nodes (%s)"
msgid "workspace.path.actions.join-nodes"
msgstr "Join nodes (%s)"
msgid "workspace.path.actions.make-corner"
msgstr "To corner (%s)"
msgid "workspace.path.actions.make-curve"
msgstr "To curve (%s)"
msgid "workspace.path.actions.merge-nodes"
msgstr "Merge nodes (%s)"
msgid "workspace.path.actions.move-nodes"
msgstr "Move nodes (%s)"
msgid "workspace.path.actions.separate-nodes"
msgstr "Separate nodes (%s)"
msgid "workspace.path.actions.snap-nodes"
msgstr "Snap nodes (%s)"
#: src/app/main/ui/workspace/context_menu.cljs
msgid "workspace.shape.menu.back"
msgstr "Send to back"

View file

@ -2153,6 +2153,36 @@ msgstr "Alineación vertical"
msgid "workspace.options.use-play-button"
msgstr "Usa el botón de play de la cabecera para arrancar la vista de prototipo."
msgid "workspace.path.actions.add-node"
msgstr "Añadir nodo (%s)"
msgid "workspace.path.actions.delete-node"
msgstr "Borrar nodos (%s)"
msgid "workspace.path.actions.draw-nodes"
msgstr "Dibujar nodos (%s)"
msgid "workspace.path.actions.join-nodes"
msgstr "Unir nodos (%s)"
msgid "workspace.path.actions.make-corner"
msgstr "Convertir en esquina (%s)"
msgid "workspace.path.actions.make-curve"
msgstr "Convertir en curva (%s)"
msgid "workspace.path.actions.merge-nodes"
msgstr "Fusionar nodos (%s)"
msgid "workspace.path.actions.move-nodes"
msgstr "Mover nodes (%s)"
msgid "workspace.path.actions.separate-nodes"
msgstr "Separar nodos (%s)"
msgid "workspace.path.actions.snap-nodes"
msgstr "Alinear nodos (%s)"
#: src/app/main/ui/workspace/context_menu.cljs
msgid "workspace.shape.menu.back"
msgstr "Enviar al fondo"