0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-03-28 15:41:25 -05:00

Components annotations

This commit is contained in:
Pablo Alba 2023-04-27 13:26:32 +02:00 committed by Andrés Moya
parent cd1825d97a
commit 68367b002e
19 changed files with 495 additions and 38 deletions

View file

@ -78,7 +78,7 @@
(update :data remove-not-allowed-pages (:pages perms))
:always
(update :data select-keys [:id :options :pages :pages-index]))))))
(update :data select-keys [:id :options :pages :pages-index :components]))))))
(s/def ::get-view-only-bundle
(s/keys :req-un [::files/file-id]

View file

@ -639,11 +639,13 @@
:id id
:name (:name new-component)
:path (:path new-component)
:annotation (:annotation new-component)
:objects (:objects new-component)}) ;; this won't exist in components-v2
(update :undo-changes d/preconj {:type :mod-component
:id id
:name (:name prev-component)
:path (:path prev-component)
:annotation (:annotation prev-component)
:objects (:objects prev-component)}))
changes)))

View file

@ -22,6 +22,8 @@
(s/def ::page-id uuid?)
(s/def ::component-id uuid?)
(s/def ::name string?)
(s/def ::path string?)
(s/def ::annotation string?)
(defmulti operation-spec :type)
@ -158,7 +160,7 @@
(defmethod change-spec :mod-component [_]
(s/keys :req-un [::id]
:opt-un [::name :internal.changes.add-component/shapes]))
:opt-un [::name ::path ::annotation :internal.changes.add-component/shapes]))
(s/def :internal.changes.del-component/skip-undelete? boolean?)

View file

@ -45,7 +45,7 @@
:main-instance-page main-instance-page))))
(defn mod-component
[file-data {:keys [id name path objects]}]
[file-data {:keys [id name path objects annotation]}]
(let [wrap-objects-fn feat/*wrap-with-objects-map-fn*]
(update-in file-data [:components id]
(fn [component]
@ -58,7 +58,13 @@
(assoc :path path)
(some? objects)
(assoc :objects objects)))))))
(assoc :objects objects)
(some? annotation)
(assoc :annotation annotation)
(nil? annotation)
(dissoc :annotation)))))))
(defn get-component
([file-data component-id]

View file

@ -383,3 +383,15 @@
.element-options > :first-child {
border-top: none;
}
.inspect-annotation {
.content {
background-color: $color-gray-60;
color: $color-white;
margin: 0 10px;
padding: 10px;
font-size: $fs14;
overflow-wrap: anywhere;
white-space: pre-wrap;
}
}

View file

@ -2327,3 +2327,120 @@
}
}
}
.component-annotation {
background-color: $color-gray-60;
border: 1px solid $color-gray-60;
&.editing {
border: 1px solid $color-primary;
}
.title {
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid $color-gray-50;
font-size: $fs12;
color: $color-gray-20;
padding: 0 10px;
&.expandeable {
cursor: pointer;
}
div {
display: flex;
align-items: center;
line-height: 2.5;
}
.expand {
svg {
fill: $color-gray-20;
width: $size-2;
height: $size-2;
margin-right: 10px;
}
}
.icon {
display: none;
cursor: pointer;
}
&:hover {
.icon {
display: flex;
svg {
fill: $color-gray-20;
width: $size-4;
height: $size-4;
margin-left: 15px;
}
.icon-tick:hover,
.icon-pencil:hover {
fill: $color-primary;
}
.icon-cross:hover,
.icon-trash:hover {
fill: $color-danger;
}
}
}
}
.hidden {
display: none;
}
.counter {
text-align: right;
font-size: $fs11;
color: $color-gray-30;
margin: 0 10px 10px 0;
}
// Auto growing text
.grow-wrap {
// easy way to plop the elements on top of each other and have them both sized based on the tallest one's height
display: grid;
&:after {
// The space is needed to preventy jumpy behavior
content: attr(data-replicated-value) " ";
white-space: pre-wrap;
visibility: hidden;
}
textarea {
background-color: $color-gray-60;
color: $color-white;
min-height: 250px;
padding: 10px;
border: none;
overflow: hidden;
outline: none;
-webkit-box-shadow: none;
-moz-box-shadow: none;
box-shadow: none;
resize: none; /*remove the resize handle on the bottom right*/
}
textarea,
&:after {
/* Identical styling required!! */
font: inherit;
font-size: $fs14;
overflow-wrap: anywhere;
padding: 10px;
/* Place on top of each other */
grid-area: 1 / 1 / 2 / 2;
}
}
}

View file

@ -141,6 +141,17 @@
(rx/reduce conj {})
(rx/map (fn [pages-index]
(update-in bundle [:file :data] assoc :pages-index pages-index))))))
(rx/mapcat
(fn [bundle]
(->> (rx/from (-> bundle :file :data seq))
(rx/merge-map
(fn [[_ object :as kp]]
(if (t/pointer? object)
(resolve kp)
(rx/of kp))))
(rx/reduce conj {})
(rx/map (fn [data]
(update bundle :file assoc :data data))))))
(rx/mapcat
(fn [{:keys [fonts] :as bundle}]
(rx/of (df/fonts-fetched fonts)

View file

@ -2060,6 +2060,58 @@
(update [_ state]
(assoc-in state [:workspace-global :file-library-reverse-sort] reverse-sort?))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Components annotations
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn update-component-annotation
"Update the component with the given annotation"
[id annotation]
(us/assert ::us/uuid id)
(us/assert ::us/string annotation)
(ptk/reify ::update-component-annotation
ptk/WatchEvent
(watch [it state _]
(let [data (get state :workspace-data)
update-fn
(fn [component]
;; NOTE: we need to ensure the component exists,
;; because there are small possibilities of race
;; conditions with component deletion.
(when component
(if (nil? annotation)
(dissoc component :annotation)
(assoc component :annotation annotation))))
changes (-> (pcb/empty-changes it)
(pcb/with-library-data data)
(pcb/update-component id update-fn))]
(rx/of (dch/commit-changes changes))))))
(defn set-annotations-expanded
[expanded?]
(ptk/reify ::set-annotations-expanded
ptk/UpdateEvent
(update [_ state]
(assoc-in state [:workspace-annotations :expanded?] expanded?))))
(defn set-annotations-id-for-create
[id]
(ptk/reify ::set-annotations-id-for-create
ptk/UpdateEvent
(update [_ state]
(if id
(-> (assoc-in state [:workspace-annotations :id-for-create] id)
(assoc-in [:workspace-annotations :expanded?] true))
(d/dissoc-in state [:workspace-annotations :id-for-create])))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Exports
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

View file

@ -0,0 +1,28 @@
;; 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.data.workspace.annotation-helpers
(:require
[app.common.data.macros :as dm]
[app.common.logging :as log]
[app.common.types.components-list :as ctkl]
[app.main.refs :as refs]))
;; Change this to :info :debug or :trace to debug this module, or :warn to reset to default
(log/set-level! :warn)
(defn get-main-annotation
[shape libraries]
(let [library (dm/get-in libraries [(:component-file shape) :data])
component (ctkl/get-component library (:component-id shape) true)]
(:annotation component)))
(defn get-main-annotation-viewer
[shape libraries]
(let [objects (deref (refs/get-viewer-objects))
parent (get objects (:parent-id shape))]
(get-main-annotation parent libraries)))

View file

@ -544,3 +544,9 @@
[id]
(l/derived #(get % id) workspace-grid-edition))
(def workspace-annotations
(l/derived #(get % :workspace-annotations {}) st/state))
(def current-file-id
(l/derived :current-file-id st/state))

View file

@ -0,0 +1,19 @@
;; 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.viewer.inspect.annotation
(:require
[app.main.ui.components.copy-button :refer [copy-button]]
[app.util.i18n :as i18n :refer [tr]]
[rumext.v2 :as mf]))
(mf/defc annotation
[{:keys [content] :as props}]
[:div.attributes-block.inspect-annotation
[:div.attributes-block-title
[:div.attributes-block-title-text (tr "workspace.options.component.annotation")]
[:& copy-button {:data content}]]
[:div.content content]])

View file

@ -7,7 +7,9 @@
(ns app.main.ui.viewer.inspect.attributes
(:require
[app.common.geom.shapes :as gsh]
[app.main.data.workspace.annotation-helpers :as dwah]
[app.main.ui.hooks :as hooks]
[app.main.ui.viewer.inspect.annotation :refer [annotation]]
[app.main.ui.viewer.inspect.attributes.blur :refer [blur-panel]]
[app.main.ui.viewer.inspect.attributes.fill :refer [fill-panel]]
[app.main.ui.viewer.inspect.attributes.image :refer [image-panel]]
@ -32,12 +34,17 @@
:text [:layout :text :shadow :blur :stroke :layout-flex-item]})
(mf/defc attributes
[{:keys [page-id file-id shapes frame from]}]
[{:keys [page-id file-id shapes frame from libraries]}]
(let [shapes (hooks/use-equal-memo shapes)
shapes (mf/with-memo [shapes]
(mapv #(gsh/translate-to-frame % frame) shapes))
type (if (= (count shapes) 1) (-> shapes first :type) :multiple)
options (type->options type)]
options (type->options type)
content (when (= (count shapes) 1)
(if (= from :workspace)
(dwah/get-main-annotation (first shapes) libraries)
(dwah/get-main-annotation-viewer (first shapes) libraries)))]
[:div.element-options
(for [[idx option] (map-indexed vector options)]
[:> (case option
@ -55,6 +62,8 @@
:shapes shapes
:frame frame
:from from}])
(when content
[:& annotation {:content content}])
[:& exports
{:shapes shapes
:type type

View file

@ -7,6 +7,7 @@
(ns app.main.ui.viewer.inspect.right-sidebar
(:require
[app.main.data.workspace :as dw]
[app.main.refs :as refs]
[app.main.store :as st]
[app.main.ui.components.shape-icon :as si]
[app.main.ui.components.tabs-container :refer [tabs-container tabs-element]]
@ -18,6 +19,24 @@
[app.util.i18n :refer [tr]]
[rumext.v2 :as mf]))
(defn- get-libraries
"Retrieve all libraries, including the local file, on workspace or viewer"
[from]
(if (= from :workspace)
(let [workspace-data (deref refs/workspace-data)
{:keys [id] :as local} workspace-data
libraries (deref refs/workspace-libraries)]
(-> libraries
(assoc id {:id id
:data local})))
(let [viewer-data (deref refs/viewer-data)
local (get-in viewer-data [:file :data])
id (deref refs/current-file-id)
libraries (:libraries viewer-data)]
(-> libraries
(assoc id {:id id
:data local})))))
(mf/defc right-sidebar
[{:keys [frame page file selected shapes page-id file-id from]
:or {from :inspect}}]
@ -28,7 +47,9 @@
first-shape (first shapes)
page-id (or page-id (:id page))
file-id (or file-id (:id file))]
file-id (or file-id (:id file))
libraries (get-libraries from)]
[:aside.settings-bar.settings-bar-right {:class (when @expanded "expanded")}
[:div.settings-bar-inside
@ -67,7 +88,8 @@
:file-id file-id
:frame frame
:shapes shapes
:from from}]]
:from from
:libraries libraries}]]
[:& tabs-element {:id :code :title (tr "inspect.tabs.code")}
[:& code {:frame frame

View file

@ -440,10 +440,9 @@
is-component? (and single? (-> shapes first :component-id some?))
is-non-root? (and single? (ctk/in-component-instance-not-root? (first shapes)))
shape-id (-> shapes first :id)
component-id (-> shapes first :component-id)
component-file (-> shapes first :component-file)
main-component? (-> shapes first :main-instance?)
first-shape (first shapes)
{:keys [shape-id component-id component-file main-instance?]} first-shape
lacks-annotation? (nil? (:annotation first-shape))
component-shapes (filter #(contains? % :component-id) shapes)
components-v2 (features/use-feature :components-v2)
@ -467,6 +466,9 @@
do-show-in-assets #(st/emit! (if components-v2
(dw/show-component-in-assets component-id)
(dw/go-to-component component-id)))
create-annotation #(when components-v2
(st/emit! (dw/set-annotations-id-for-create (:id first-shape))))
do-navigate-component-file #(st/emit! (dwl/nav-to-component-file component-file))
do-update-component #(st/emit! (dwl/update-component-sync shape-id component-file))
do-update-component-in-bulk #(st/emit! (dwl/update-component-in-bulk component-shapes component-file))
@ -518,9 +520,13 @@
;; app/main/ui/workspace/sidebar/options/menus/component.cljs
[:*
[:& menu-separator]
(if main-component?
[:& menu-entry {:title (tr "workspace.shape.menu.show-in-assets")
:on-click do-show-in-assets}]
(if main-instance?
[:*
[:& menu-entry {:title (tr "workspace.shape.menu.show-in-assets")
:on-click do-show-in-assets}]
(when (and components-v2 lacks-annotation?)
[:& menu-entry {:title (tr "workspace.shape.menu.create-annotation")
:on-click create-annotation}])]
(if local-component?
(if is-dangling?
[:*

View file

@ -20,29 +20,148 @@
[app.util.i18n :as i18n :refer [tr]]
[rumext.v2 :as mf]))
(def component-attrs [:component-id :component-file :shape-ref :main-instance?])
(def component-attrs [:component-id :component-file :shape-ref :main-instance? :annotation])
(mf/defc component-annotation
[{:keys [id values shape component] :as props}]
(let [main-instance? (:main-instance? values)
component-id (:component-id values)
annotation (:annotation component)
editing? (mf/use-state false)
size (mf/use-state (count annotation))
textarea-ref (mf/use-ref)
;; hack to create an autogrowing textarea
;; based on https://css-tricks.com/the-cleanest-trick-for-autogrowing-textareas/
autogrow #(let [textarea (mf/ref-val textarea-ref)
text (when textarea (.-value textarea))]
(when textarea
(reset! size (count text))
(aset (.-dataset (.-parentNode textarea)) "replicatedValue" text)))
initialize #(let [textarea (mf/ref-val textarea-ref)]
(when textarea
(aset textarea "value" annotation)
(autogrow)))
discard (fn [event]
(dom/stop-propagation event)
(let [textarea (mf/ref-val textarea-ref)]
(aset textarea "value" annotation)
(reset! editing? false)
(st/emit! (dw/set-annotations-id-for-create nil))))
save (fn [event]
(dom/stop-propagation event)
(let [textarea (mf/ref-val textarea-ref)
text (.-value textarea)]
(reset! editing? false)
(st/emit!
(dw/set-annotations-id-for-create nil)
(dw/update-component-annotation component-id text))))
workspace-annotations (mf/deref refs/workspace-annotations)
annotations-expanded? (:expanded? workspace-annotations)
creating? (= id (:id-for-create workspace-annotations))
expand #(when-not (or @editing? creating?)
(st/emit! (dw/set-annotations-expanded %)))
edit (fn [event]
(dom/stop-propagation event)
(when main-instance?
(let [textarea (mf/ref-val textarea-ref)]
(reset! editing? true)
(dom/focus! textarea))))
on-delete-annotation
(mf/use-callback
(mf/deps shape)
(fn [event]
(dom/stop-propagation event)
(st/emit! (modal/show
{:type :confirm
:title (tr "modals.delete-component-annotation.title")
:message (tr "modals.delete-component-annotation.message")
:accept-label (tr "ds.confirm-ok")
:on-accept (fn []
(st/emit!
(dw/set-annotations-id-for-create nil)
(dw/update-component-annotation component-id nil)))}))))]
(mf/use-effect
(mf/deps shape)
(fn []
(initialize)
(when (and (not creating?) (:id-for-create workspace-annotations)) ;; cleanup set-annotations-id-for-create if we aren't on the marked component
(st/emit! (dw/set-annotations-id-for-create nil)))
(fn [] (st/emit! (dw/set-annotations-id-for-create nil))))) ;; cleanup set-annotationsid-for-create on unload
(when (or creating? annotation)
[:div.component-annotation {:class (dom/classnames :editing @editing?)}
[:div.title {:class (dom/classnames :expandeable (not (or @editing? creating?)))
:on-click #(expand (not annotations-expanded?))}
[:div (if (or @editing? creating?)
(if @editing?
(tr "workspace.options.component.edit-annotation")
(tr "workspace.options.component.create-annotation"))
[:* (if annotations-expanded?
[:div.expand i/arrow-down]
[:div.expand i/arrow-slide])
(tr "workspace.options.component.annotation")])]
[:div
(when (and main-instance? annotations-expanded?)
(if (or @editing? creating?)
[:*
[:div.icon {:title (if creating? (tr "labels.create") (tr "labels.save"))
:on-click save} i/tick]
[:div.icon {:title (tr "labels.discard")
:on-click discard} i/cross]]
[:*
[:div.icon {:title (tr "labels.edit")
:on-click edit} i/pencil]
[:div.icon {:title (tr "labels.delete")
:on-click on-delete-annotation} i/trash]]))]]
[:div {:class (dom/classnames :hidden (not annotations-expanded?))}
[:div.grow-wrap
[:div.texarea-copy]
[:textarea
{:ref textarea-ref
:id "annotation-textarea"
:data-debug annotation
:auto-focus true
:maxLength 300
:on-input autogrow
:default-value annotation
:read-only (not (or creating? @editing?))}]]
(when (or @editing? creating?)
[:div.counter (str @size "/300")])]])))
(mf/defc component-menu
[{:keys [ids values shape-name] :as props}]
(let [current-file-id (mf/use-ctx ctx/current-file-id)
components-v2 (mf/use-ctx ctx/components-v2)
[{:keys [ids values shape] :as props}]
(let [current-file-id (mf/use-ctx ctx/current-file-id)
components-v2 (mf/use-ctx ctx/components-v2)
id (first ids)
local (mf/use-state {:menu-open false})
id (first ids)
local (mf/use-state {:menu-open false})
component-id (:component-id values)
library-id (:component-file values)
show? (some? component-id)
main-instance? (if components-v2
(:main-instance? values)
true)
main-component? (:main-instance? values)
shape-name (:name shape)
component-id (:component-id values)
library-id (:component-file values)
show? (some? component-id)
main-instance? (if components-v2
(:main-instance? values)
true)
main-component? (:main-instance? values)
lacks-annotation? (nil? (:annotation values))
local-component? (= library-id current-file-id)
workspace-data (deref refs/workspace-data)
workspace-libraries (deref refs/workspace-libraries)
is-dangling? (nil? (if local-component?
(ctkl/get-component workspace-data component-id)
(ctf/get-component workspace-libraries library-id component-id)))
component (if local-component?
(ctkl/get-component workspace-data component-id)
(ctf/get-component workspace-libraries library-id component-id))
is-dangling? (nil? component)
on-menu-click
(mf/use-callback
@ -82,6 +201,7 @@
do-show-in-assets #(st/emit! (if components-v2
(dw/show-component-in-assets component-id)
(dw/go-to-component component-id)))
do-create-annotation #(st/emit! (dw/set-annotations-id-for-create id))
do-navigate-component-file #(st/emit! (dwl/nav-to-component-file library-id))]
(when show?
[:div.element-set
@ -103,7 +223,9 @@
:show (:menu-open @local)
:options
(if main-component?
[[(tr "workspace.shape.menu.show-in-assets") do-show-in-assets]]
[[(tr "workspace.shape.menu.show-in-assets") do-show-in-assets]
(when (and components-v2 lacks-annotation?)
[(tr "workspace.shape.menu.create-annotation") do-create-annotation])]
(if local-component?
(if is-dangling?
[[(tr "workspace.shape.menu.detach-instance") do-detach-component]
@ -124,4 +246,7 @@
[[(tr "workspace.shape.menu.detach-instance") do-detach-component]
[(tr "workspace.shape.menu.reset-overrides") do-reset-component]
[(tr "workspace.shape.menu.update-main") do-update-remote-component]
[(tr "workspace.shape.menu.go-main") do-navigate-component-file]])))}]]]]])))
[(tr "workspace.shape.menu.go-main") do-navigate-component-file]])))}]]]
(when components-v2
[:& component-annotation {:id id :values values :shape shape :component component}])]])))

View file

@ -47,7 +47,7 @@
:shape shape}]
[:& component-menu {:ids comp-ids
:values comp-values
:shape-name (:name shape)}]
:shape shape}]
(when (or (not is-flex-layout-child?) is-layout-child-absolute?)
[:& constraints-menu {:ids ids
:values constraint-values}])

View file

@ -55,7 +55,7 @@
[:div.options
[:& measures-menu {:type type :ids measure-ids :values measure-values :shape shape}]
[:& component-menu {:ids comp-ids :values comp-values :shape-name (:name shape)}]
[:& component-menu {:ids comp-ids :values comp-values :shape shape}] ;;remove this in components-v2
[:& layout-container-menu {:type type :ids [(:id shape)] :values layout-container-values :multiple false}]
(when is-flex-layout-child?

View file

@ -314,7 +314,7 @@ msgstr "Create token"
#: src/app/main/ui/settings/access-tokens.cljs
msgid "modals.delete-acces-token.title"
msgstr "Delete token"
#: src/app/main/ui/settings/access-tokens.cljs
msgid "modals.delete-acces-token.message"
msgstr "Are you sure you want to delete this token?"
@ -1337,6 +1337,9 @@ msgstr "Copy link"
msgid "labels.create"
msgstr "Create"
msgid "labels.discard"
msgstr "Discard"
#: src/app/main/ui/dashboard/team_form.cljs, src/app/main/ui/dashboard/team_form.cljs
msgid "labels.create-team"
msgstr "Create new team"
@ -2124,6 +2127,12 @@ msgstr ""
msgid "modals.update-remote-component.message"
msgstr "Update a component in a shared library"
msgid "modals.delete-component-annotation.message"
msgstr "Are you sure you want to delete this annotation?"
msgid "modals.delete-component-annotation.title"
msgstr "Delete annotation"
#: src/app/main/ui/dashboard/team.cljs
msgid "notifications.invitation-email-sent"
msgstr "Invitation sent successfully"
@ -3368,6 +3377,15 @@ msgstr "Clip content"
msgid "workspace.options.component"
msgstr "Component"
msgid "workspace.options.component.annotation"
msgstr "Annotation"
msgid "workspace.options.component.create-annotation"
msgstr "Create an annotation"
msgid "workspace.options.component.edit-annotation"
msgstr "Edit an annotation"
#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs
msgid "workspace.options.constraints"
msgstr "Constraints"
@ -4451,6 +4469,9 @@ msgstr "Show in assets panel"
msgid "workspace.shape.menu.show-main"
msgstr "Show main component"
msgid "workspace.shape.menu.create-annotation"
msgstr "Create annotation"
msgid "workspace.shape.menu.thumbnail-remove"
msgstr "Remove thumbnail"
@ -4713,4 +4734,5 @@ msgid "workspace.updates.update"
msgstr "Update"
msgid "workspace.viewport.click-to-close-path"
msgstr "Click to close the path"
msgstr "Click to close the path"

View file

@ -321,7 +321,7 @@ msgstr "Crear token"
#: src/app/main/ui/settings/access-tokens.cljs
msgid "modals.delete-acces-token.title"
msgstr "Borrar token"
#: src/app/main/ui/settings/access-tokens.cljs
msgid "modals.delete-acces-token.message"
msgstr "¿Seguro que deseas borrar este token?"
@ -2192,6 +2192,12 @@ msgstr ""
msgid "modals.update-remote-component.message"
msgstr "Actualizar un componente en biblioteca"
msgid "modals.delete-component-annotation.message"
msgstr "¿Seguro que quieres borrar esta nota?"
msgid "modals.delete-component-annotation.title"
msgstr "Borrar nota"
#: src/app/main/ui/dashboard/team.cljs
msgid "notifications.invitation-email-sent"
msgstr "Invitación enviada con éxito"
@ -3463,6 +3469,15 @@ msgstr "Truncar contenido"
msgid "workspace.options.component"
msgstr "Componente"
msgid "workspace.options.component.annotation"
msgstr "Nota"
msgid "workspace.options.component.create-annotation"
msgstr "Crear una nota"
msgid "workspace.options.component.edit-annotation"
msgstr "Editar una nota"
#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs
msgid "workspace.options.constraints"
msgstr "Restricciones"
@ -4570,6 +4585,9 @@ msgstr "Ver en el panel de recursos"
msgid "workspace.shape.menu.show-main"
msgstr "Ver componente principal"
msgid "workspace.shape.menu.create-annotation"
msgstr "Crear una nota"
msgid "workspace.shape.menu.thumbnail-remove"
msgstr "Quitar miniatura"