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

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