0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-04-10 05:51:33 -05:00

🎉 Group components and graphics in assets sidebar

This commit is contained in:
Andrés Moya 2021-04-06 09:22:30 +02:00
parent eff333cbaf
commit 3613e6f3d3
14 changed files with 525 additions and 107 deletions

View file

@ -5,6 +5,7 @@
### :sparkles: New features
- Add integration with gitpod.io (an online IDE) [#807](https://github.com/penpot/penpot/pull/807)
- Allow to group assets (components and graphics) [Taiga #1289](https://tree.taiga.io/project/penpot/us/1289)
- Internal: refactor of http client, replace internal xhr usage with more modern Fetch API.

View file

@ -62,6 +62,9 @@
(d/export helpers/get-index-in-parent)
(d/export helpers/calculate-z-index)
(d/export helpers/generate-child-all-parents-index)
(d/export helpers/parse-path-name)
(d/export helpers/merge-path-item)
(d/export helpers/compact-path)
;; Process changes
(d/export changes/process-changes)

View file

@ -364,21 +364,25 @@
;; -- Components
(defmethod process-change :add-component
[data {:keys [id name shapes]}]
[data {:keys [id name path shapes]}]
(assoc-in data [:components id]
{:id id
:name name
:path path
:objects (d/index-by :id shapes)}))
(defmethod process-change :mod-component
[data {:keys [id name objects]}]
[data {:keys [id name path objects]}]
(update-in data [:components id]
#(cond-> %
(some? name)
(assoc :name name)
(some? name)
(assoc :name name)
(some? objects)
(assoc :objects objects))))
(some? path)
(assoc :path path)
(some? objects)
(assoc :objects objects))))
(defmethod process-change :del-component
[data {:keys [id]}]

View file

@ -9,7 +9,8 @@
[app.common.data :as d]
[app.common.geom.shapes :as gsh]
[app.common.spec :as us]
[app.common.uuid :as uuid]))
[app.common.uuid :as uuid]
[cuerdas.core :as str]))
(defn walk-pages
"Go through all pages of a file and apply a function to each one"
@ -332,7 +333,6 @@
(d/concat new-children new-child-objects)
(d/concat updated-children updated-child-objects))))))))
(defn indexed-shapes
"Retrieves a list with the indexes for each element in the layer tree.
This will be used for shift+selection."
@ -459,3 +459,44 @@
[parent-idx _] (d/seek (fn [[idx child-id]] (= child-id shape-id))
(d/enumerate (:shapes parent)))]
parent-idx))
(defn parse-path-name
"Parse a string in the form 'group / subgroup / name'.
Retrieve the path and the name in separated values, normalizing spaces."
[path-name]
(let [path-name-split (->> (str/split path-name "/")
(map str/trim)
(remove str/empty?))
path (str/join " / " (butlast path-name-split))
name (last path-name-split)]
[path name]))
(defn merge-path-item
"Put the item at the end of the path."
[path name]
(if-not (empty? path)
(str path " / " name)
name))
(defn compact-path
"Separate last component of the path, and truncate the others if too long:
'one' -> ['' 'one' false]
'one / two / three' -> ['one / two' 'three' false]
'one / two / three / four' -> ['one / two / ...' 'four' true]
'one-item-but-very-long / two' -> ['...' 'two' true] "
[path max-length]
(let [path-split (->> (str/split path "/")
(map str/trim))
last-item (last path-split)]
(loop [other-items (seq (butlast path-split))
other-path ""]
(if-let [item (first other-items)]
(let [full-path (-> other-path
(merge-path-item item)
(merge-path-item last-item))]
(if (> (count full-path) max-length)
[(merge-path-item other-path "...") last-item true]
(recur (next other-items)
(merge-path-item other-path item))))
[other-path last-item false]))))

View file

@ -16,6 +16,7 @@
(s/def ::frame-id uuid?)
(s/def ::id uuid?)
(s/def ::name string?)
(s/def ::path string?)
(s/def ::page-id uuid?)
(s/def ::parent-id uuid?)
(s/def ::string string?)
@ -547,7 +548,8 @@
(s/coll-of ::shape))
(defmethod change-spec :add-component [_]
(s/keys :req-un [::id ::name :internal.changes.add-component/shapes]))
(s/keys :req-un [::id ::name :internal.changes.add-component/shapes]
:opt-un [::path]))
(defmethod change-spec :mod-component [_]
(s/keys :req-un [::id]

View file

@ -74,7 +74,7 @@ ul.palette-menu .color-bullet {
background-size: 8px;
}
.asset-group .group-list-item .color-bullet {
.asset-section .asset-list-item .color-bullet {
border: 1px solid $color-gray-20;
border-radius: 10px;
height: 20px;

View file

@ -118,7 +118,7 @@
cursor: pointer;
}
.asset-group {
.asset-section {
background-color: $color-gray-60;
border-top: 1px solid $color-gray-50;
padding: $small;
@ -126,33 +126,58 @@
color: $color-gray-20;
/* TODO: see if this is useful, or is better to leave only
one scroll bar in the whole sidebar
(also see .group-list) */
(also see .asset-list) */
// max-height: 30rem;
// overflow-y: scroll;
.group-title {
display: flex;
cursor: pointer;
.asset-title {
display: flex;
cursor: pointer;
& .num-assets {
color: $color-gray-30;
}
& svg {
height: 8px;
width: 8px;
fill: $color-gray-30;
margin-right: 4px;
transform: rotate(90deg);
height: 8px;
width: 8px;
fill: $color-gray-30;
margin-right: 4px;
transform: rotate(90deg);
}
&.closed svg {
transform: rotate(0deg);
transition: transform 0.3s;
transform: rotate(0deg);
transition: transform 0.3s;
}
}
.group-button {
.group-title {
display: flex;
cursor: pointer;
margin-top: $small;
margin-bottom: $x-small;
color: $color-white;
& svg {
height: 8px;
width: 8px;
fill: $color-white;
margin-right: 4px;
transform: rotate(90deg);
}
&.closed svg {
transform: rotate(0deg);
transition: transform 0.3s;
}
& .dim {
color: $color-gray-40;
}
}
.assets-button {
margin-left: auto;
cursor: pointer;
@ -167,8 +192,11 @@
}
}
.group-grid {
margin-top: $medium;
.asset-title + .asset-grid {
margin-top: $small;
}
.asset-grid {
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
grid-auto-rows: 6vh;
@ -192,6 +220,7 @@
.grid-cell {
background-color: $color-canvas;
border-radius: 4px;
border: 2px solid transparent;
overflow: hidden;
display: flex;
align-items: center;
@ -242,26 +271,30 @@
}
.grid-cell:hover {
border: 1px solid $color-primary;
border: 2px solid $color-primary;
& .cell-name {
display: block;
}
}
.grid-cell.selected {
border: 2px solid $color-primary;
}
/* TODO: see if this is useful, or is better to leave only
one scroll bar in the whole sidebar
(also see .asset-group) */
// .group-list {
(also see .asset-section) */
// .asset-list {
// max-height: 30rem;
// overflow-y: scroll;
// }
.group-list {
.asset-list {
margin-top: $medium;
}
.group-list-item {
.asset-list-item {
display: flex;
align-items: center;
margin-top: $small;

View file

@ -983,7 +983,7 @@
display: flex;
}
.asset-group {
.asset-section {
.typography-entry {
margin: 0.25rem 0;
}

View file

@ -157,14 +157,17 @@
ptk/WatchEvent
(watch [_ state stream]
(let [object (get-in state [:workspace-data :media id])
[path name] (cp/parse-path-name new-name)
rchanges [{:type :mod-media
:object {:id id
:name new-name}}]
:name name
:path path}}]
uchanges [{:type :mod-media
:object {:id id
:name (:name object)}}]]
:name (:name object)
:path (:path object)}}]]
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true}))))))
@ -250,20 +253,24 @@
(ptk/reify ::rename-component
ptk/WatchEvent
(watch [_ state stream]
(let [component (get-in state [:workspace-data :components id])
(let [[path name] (cp/parse-path-name new-name)
component (get-in state [:workspace-data :components id])
objects (get component :objects)
; Give the same name to the root shape
new-objects (assoc-in objects
[(:id component) :name]
new-name)
name)
rchanges [{:type :mod-component
:id id
:name new-name
:name name
:path path
:objects new-objects}]
uchanges [{:type :mod-component
:id id
:name (:name component)
:path (:path component)
:objects objects}]]
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true}))))))
@ -288,6 +295,7 @@
rchanges [{:type :add-component
:id (:id new-shape)
:name new-name
:path (:path component)
:shapes new-shapes}]
uchanges [{:type :del-component

View file

@ -14,8 +14,9 @@
[rumext.alpha :as mf]))
(mf/defc editable-label
[{:keys [value on-change on-cancel editing? disable-dbl-click? class-name]}]
(let [input (mf/use-ref nil)
[{:keys [value on-change on-cancel editing? disable-dbl-click? class-name] :as props}]
(let [display-value (get props :display-value value)
input (mf/use-ref nil)
state (mf/use-state (:editing false))
is-editing (:editing @state)
start-editing (fn []
@ -53,4 +54,4 @@
:on-blur cancel-editing}]
[:span.editable-label-close {:on-click cancel-editing} i/close]]
[:span.editable-label {:class class-name
:on-double-click on-dbl-click} value])))
:on-double-click on-dbl-click} display-value])))

View file

@ -23,6 +23,7 @@
(let [input-type (get props :type "text")
input-name (get props :name)
more-classes (get props :class)
auto-focus? (get props :auto-focus? false)
form (or form (mf/use-ctx form-ctx))
@ -84,6 +85,7 @@
(dissoc :help-icon :form :trim)
(assoc :id (name input-name)
:value value
:auto-focus auto-focus?
:on-focus on-focus
:on-blur on-blur
:placeholder label

View file

@ -7,6 +7,7 @@
(ns app.main.ui.workspace.sidebar.assets
(:require
[app.common.data :as d]
[app.common.spec :as us]
[app.common.geom.point :as gpt]
[app.common.geom.shapes :as geom]
[app.common.media :as cm]
@ -17,6 +18,7 @@
[app.main.data.colors :as dc]
[app.main.data.modal :as modal]
[app.main.data.workspace :as dw]
[app.main.data.workspace.common :as dwc]
[app.main.data.workspace.libraries :as dwl]
[app.main.data.workspace.texts :as dwt]
[app.main.exports :as exports]
@ -26,6 +28,7 @@
[app.main.ui.components.context-menu :refer [context-menu]]
[app.main.ui.components.editable-label :refer [editable-label]]
[app.main.ui.components.file-uploader :refer [file-uploader]]
[app.main.ui.components.forms :as fm]
[app.main.ui.components.tab-container :refer [tab-container tab-element]]
[app.main.ui.context :as ctx]
[app.main.ui.icons :as i]
@ -37,17 +40,127 @@
[app.util.keyboard :as kbd]
[app.util.router :as rt]
[app.util.timers :as timers]
[cljs.spec.alpha :as s]
[cuerdas.core :as str]
[okulary.core :as l]
[rumext.alpha :as mf]))
;; ---- Assets selection management
(def empty-selection #{})
(defn toggle-select
[selected asset-id]
(if (contains? selected asset-id)
(disj selected asset-id)
(conj selected asset-id)))
(defn replace-select
[selected asset-id]
#{asset-id})
(defn extend-select
[selected asset-id groups]
(let [assets (->> groups vals flatten)
clicked-idx (d/index-of-pred assets #(= (:id %) asset-id))
selected-idx (->> selected
(map (fn [id] (d/index-of-pred assets
#(= (:id %) id)))))
min-idx (apply min (conj selected-idx clicked-idx))
max-idx (apply max (conj selected-idx clicked-idx))]
(->> assets
d/enumerate
(filter #(<= min-idx (first %) max-idx))
(map #(-> % second :id))
set)))
;; ---- Group assets management ----
(s/def ::asset-name ::us/not-empty-string)
(s/def ::create-group-form
(s/keys :req-un [::asset-name]))
(defn group-assets
[assets]
(reduce (fn [groups asset]
(update groups (or (:path asset) "")
#(conj (or % []) asset)))
(sorted-map)
assets))
(def empty-folded-groups #{})
(defn toggle-folded-group
[folded-groups path]
(if (contains? folded-groups path)
(disj folded-groups path)
(conj folded-groups path)))
(mf/defc create-group-dialog
{::mf/register modal/components
::mf/register-as :create-group-dialog}
[{:keys [create] :as ctx}]
(let [form (fm/use-form :spec ::create-group-form
:initial {})
close #(modal/hide!)
on-accept
(mf/use-callback
(mf/deps form)
(fn [event]
(let [asset-name (get-in @form [:clean-data :asset-name])]
(create asset-name)
(modal/hide!))))]
[:div.modal-overlay
[:div.modal-container.confirm-dialog
[:div.modal-header
[:div.modal-header-title
[:h2 (tr "workspace.assets.create-group")]]
[:div.modal-close-button
{:on-click close} i/close]]
[:div.modal-content.generic-form
[:& fm/form {:form form}
[:& fm/input {:name :asset-name
:auto-focus? true
:label (tr "workspace.assets.group-name")
:hint (tr "workspace.assets.create-group-hint")}]]]
[:div.modal-footer
[:div.action-buttons
[:input.cancel-button
{:type "button"
:value (tr "labels.cancel")
:on-click close}]
[:input.accept-button.primary
{:type "button"
:class (when-not (:valid @form) "btn-disabled")
:disabled (not (:valid @form))
:value (tr "labels.create")
:on-click on-accept}]]]]]))
;; ---- Components box ----
(mf/defc components-box
[{:keys [file-id local? components open?] :as props}]
(let [state (mf/use-state {:menu-open false
:renaming nil
:top nil
:left nil
:component-id nil})
:component-id nil
:selected empty-selection
:folded-groups empty-folded-groups})
groups (group-assets components)
selected (:selected @state)
folded-groups (:folded-groups @state)
on-duplicate
(mf/use-callback
@ -94,6 +207,60 @@
:left left
:component-id component-id))))))
unselect-all
(mf/use-callback
(fn [event]
(swap! state assoc :selected empty-selection)))
on-select
(mf/use-callback
(mf/deps state)
(fn [component-id]
(fn [event]
(dom/stop-propagation event)
(swap! state update :selected
(fn [selected]
(cond
(kbd/ctrl? event)
(toggle-select selected component-id)
(kbd/shift? event)
(extend-select selected component-id groups)
:default
(replace-select selected component-id)))))))
create-group
(mf/use-callback
(mf/deps components selected)
(fn [name]
(swap! state assoc :selected empty-selection)
(st/emit! (dwc/start-undo-transaction))
(apply st/emit!
(->> components
(filter #(contains? selected (:id %)))
(map #(dwl/rename-component
(:id %)
(str name " / "
(cp/merge-path-item (:path %) (:name %)))))))
(st/emit! (dwc/commit-undo-transaction))))
on-fold-group
(mf/use-callback
(mf/deps groups folded-groups)
(fn [path]
(fn [event]
(dom/stop-propagation event)
(swap! state update :folded-groups
toggle-folded-group path))))
on-group
(mf/use-callback
(mf/deps components selected)
(fn [event]
(dom/stop-propagation event)
(modal/show! :create-group-dialog {:create create-group})))
on-drag-start
(mf/use-callback
(fn [component event]
@ -101,30 +268,50 @@
:component component})
(dnd/set-allowed-effect! event "move")))]
[:div.asset-group
[:div.group-title {:class (when (not open?) "closed")}
[:div.asset-section {:on-click unselect-all}
[:div.asset-title {:class (when (not open?) "closed")}
[:span {:on-click (st/emitf (dwl/set-assets-box-open file-id :components (not open?)))}
i/arrow-slide (tr "workspace.assets.components")]
[:span (str "\u00A0(") (count components) ")"]] ;; Unicode 00A0 is non-breaking space
(when open?
[:div.group-grid.big
(for [component components]
(let [renaming? (= (:renaming @state)(:id component))]
[:div.grid-cell {:key (:id component)
:draggable true
:on-context-menu (on-context-menu (:id component))
:on-drag-start (partial on-drag-start component)}
[:& exports/component-svg {:group (get-in component [:objects (:id component)])
:objects (:objects component)}]
[:& editable-label
{:class-name (dom/classnames
:cell-name true
:editing renaming?)
:value (:name component)
:editing? renaming?
:disable-dbl-click? true
:on-change do-rename
:on-cancel cancel-rename}]]))])
(for [group groups]
(let [path (first group)
components (second group)
group-open? (not (contains? folded-groups path))]
[:*
(when-not (empty? path)
(let [[other-path last-path truncated] (cp/compact-path path 35)]
[:div.group-title {:class (when-not group-open? "closed")
:on-click (on-fold-group path)}
[:span i/arrow-slide]
(when-not (empty? other-path)
[:span.dim {:title (when truncated path)}
other-path "\u00A0/\u00A0"])
[:span {:title (when truncated path)}
last-path]]))
(when group-open?
[:div.asset-grid.big
(for [component components]
(let [renaming? (= (:renaming @state)(:id component))]
[:div.grid-cell {:key (:id component)
:class-name (dom/classnames
:selected (contains? selected (:id component)))
:draggable true
:on-click (on-select (:id component))
:on-context-menu (on-context-menu (:id component))
:on-drag-start (partial on-drag-start component)}
[:& exports/component-svg {:group (get-in component [:objects (:id component)])
:objects (:objects component)}]
[:& editable-label
{:class-name (dom/classnames
:cell-name true
:editing renaming?)
:value (cp/merge-path-item (:path component) (:name component))
:display-value (:name component)
:editing? renaming?
:disable-dbl-click? true
:on-change do-rename
:on-cancel cancel-rename}]]))])])))
(when local?
[:& context-menu
@ -133,9 +320,14 @@
:on-close #(swap! state assoc :menu-open false)
:top (:top @state)
:left (:left @state)
:options [[(tr "workspace.assets.rename") on-rename]
:options [(when (<= (count selected) 1)
[(tr "workspace.assets.rename") on-rename])
[(tr "workspace.assets.duplicate") on-duplicate]
[(tr "workspace.assets.delete") on-delete]]}])]))
[(tr "workspace.assets.delete") on-delete]
[(tr "workspace.assets.group") on-group]]}])]))
;; ---- Graphics box ----
(mf/defc graphics-box
[{:keys [file-id local? objects open?] :as props}]
@ -144,7 +336,13 @@
:renaming nil
:top nil
:left nil
:object-id nil})
:object-id nil
:selected empty-selection
:folded-groups empty-folded-groups})
groups (group-assets objects)
selected (:selected @state)
folded-groups (:folded-groups @state)
add-graphic
(mf/use-callback
@ -200,6 +398,60 @@
:left left
:object-id object-id))))))
unselect-all
(mf/use-callback
(fn [event]
(swap! state assoc :selected empty-selection)))
on-select
(mf/use-callback
(mf/deps state)
(fn [object-id]
(fn [event]
(dom/stop-propagation event)
(swap! state update :selected
(fn [selected]
(cond
(kbd/ctrl? event)
(toggle-select selected object-id)
(kbd/shift? event)
(extend-select selected object-id groups)
:default
(replace-select selected object-id)))))))
create-group
(mf/use-callback
(mf/deps objects selected)
(fn [name]
(swap! state assoc :selected empty-selection)
(st/emit! (dwc/start-undo-transaction))
(apply st/emit!
(->> objects
(filter #(contains? selected (:id %)))
(map #(dwl/rename-media
(:id %)
(str name " / "
(cp/merge-path-item (:path %) (:name %)))))))
(st/emit! (dwc/commit-undo-transaction))))
on-fold-group
(mf/use-callback
(mf/deps groups folded-groups)
(fn [path]
(fn [event]
(dom/stop-propagation event)
(swap! state update :folded-groups
toggle-folded-group path))))
on-group
(mf/use-callback
(mf/deps objects selected)
(fn [event]
(dom/stop-propagation event)
(modal/show! :create-group-dialog {:create create-group})))
on-drag-start
(mf/use-callback
(fn [{:keys [name id mtype]} event]
@ -208,49 +460,74 @@
(dnd/set-data! event "text/asset-type" mtype)
(dnd/set-allowed-effect! event "move")))]
[:div.asset-group
[:div.group-title {:class (when (not open?) "closed")}
[:div.asset-section {:on-click unselect-all}
[:div.asset-title {:class (when (not open?) "closed")}
[:span {:on-click (st/emitf (dwl/set-assets-box-open file-id :graphics (not open?)))}
i/arrow-slide (tr "workspace.assets.graphics")]
[:span.num-assets (str "\u00A0(") (count objects) ")"] ;; Unicode 00A0 is non-breaking space
(when local?
[:div.group-button {:on-click add-graphic}
[:div.assets-button {:on-click add-graphic}
i/plus
[:& file-uploader {:accept cm/str-media-types
:multi true
:input-ref input-ref
:on-selected on-selected}]])]
(when open?
[:div.group-grid
(for [object objects]
[:div.grid-cell {:key (:id object)
:draggable true
:on-context-menu (on-context-menu (:id object))
:on-drag-start (partial on-drag-start object)}
[:img {:src (cfg/resolve-file-media object true)
:draggable false}] ;; Also need to add css pointer-events: none
(for [group groups]
(let [path (first group)
objects (second group)
group-open? (not (contains? folded-groups path))]
[:*
(when-not (empty? path)
(let [[other-path last-path truncated] (cp/compact-path path 35)]
[:div.group-title {:class (when-not group-open? "closed")
:on-click (on-fold-group path)}
[:span i/arrow-slide]
(when-not (empty? other-path)
[:span.dim {:title (when truncated path)}
other-path "\u00A0/\u00A0"])
[:span {:title (when truncated path)}
last-path]]))
(when group-open?
[:div.asset-grid
(for [object objects]
[:div.grid-cell {:key (:id object)
:class-name (dom/classnames
:selected (contains? selected (:id object)))
:draggable true
:on-click (on-select (:id object))
:on-context-menu (on-context-menu (:id object))
:on-drag-start (partial on-drag-start object)}
[:img {:src (cfg/resolve-file-media object true)
:draggable false}] ;; Also need to add css pointer-events: none
#_[:div.cell-name (:name object)]
(let [renaming? (= (:renaming @state) (:id object))]
[:& editable-label
{:class-name (dom/classnames
:cell-name true
:editing renaming?)
:value (:name object)
:editing? renaming?
:disable-dbl-click? true
:on-change do-rename
:on-cancel cancel-rename}])])
#_[:div.cell-name (:name object)]
(let [renaming? (= (:renaming @state) (:id object))]
[:& editable-label
{:class-name (dom/classnames
:cell-name true
:editing renaming?)
:value (cp/merge-path-item (:path object) (:name object))
:display-value (:name object)
:editing? renaming?
:disable-dbl-click? true
:on-change do-rename
:on-cancel cancel-rename}])])])])))
(when local?
[:& context-menu
{:selectable false
:show (:menu-open @state)
:on-close #(swap! state assoc :menu-open false)
:top (:top @state)
:left (:left @state)
:options [[(tr "workspace.assets.rename") on-rename]
[(tr "workspace.assets.delete") on-delete]]}])])]))
(when local?
[:& context-menu
{:selectable false
:show (:menu-open @state)
:on-close #(swap! state assoc :menu-open false)
:top (:top @state)
:left (:left @state)
:options [(when (<= (count selected) 1)
[(tr "workspace.assets.rename") on-rename])
[(tr "workspace.assets.delete") on-delete]
[(tr "workspace.assets.group") on-group]]}])]))
;; ---- Colors box ----
(mf/defc color-item
[{:keys [color local? file-id locale] :as props}]
@ -337,7 +614,7 @@
(dom/select-text! input))
nil))
[:div.group-list-item {:on-context-menu on-context-menu}
[:div.asset-list-item {:on-context-menu on-context-menu}
[:& bc/color-bullet {:color color
:on-click click-color}]
@ -387,15 +664,15 @@
:data {:color "#406280"
:opacity 1}
:position :right})))]
[:div.asset-group
[:div.group-title {:class (when (not open?) "closed")}
[:div.asset-section
[:div.asset-title {:class (when (not open?) "closed")}
[:span {:on-click (st/emitf (dwl/set-assets-box-open file-id :colors (not open?)))}
i/arrow-slide (t locale "workspace.assets.colors")]
[:span.num-assets (str "\u00A0(") (count colors) ")"] ;; Unicode 00A0 is non-breaking space
(when local?
[:div.group-button {:on-click add-color-clicked} i/plus])]
[:div.assets-button {:on-click add-color-clicked} i/plus])]
(when open?
[:div.group-list
[:div.asset-list
(for [color colors]
(let [color (cond-> color
(:value color) (assoc :color (:value color) :opacity 1)
@ -407,6 +684,9 @@
:local? local?
:locale locale}]))])]))
;; ---- Typography box ----
(mf/defc typography-box
[{:keys [file file-id local? typographies locale open?] :as props}]
@ -480,13 +760,13 @@
(when (:edit-typography local)
(st/emit! #(update % :workspace-local dissoc :edit-typography)))))
[:div.asset-group
[:div.group-title {:class (when (not open?) "closed")}
[:div.asset-section
[:div.asset-title {:class (when (not open?) "closed")}
[:span {:on-click (st/emitf (dwl/set-assets-box-open file-id :typographies (not open?)))}
i/arrow-slide (t locale "workspace.assets.typography")]
[:span.num-assets (str "\u00A0(") (count typographies) ")"] ;; Unicode 00A0 is non-breaking space
(when local?
[:div.group-button {:on-click add-typography} i/plus])]
[:div.assets-button {:on-click add-typography} i/plus])]
[:& context-menu
{:selectable false
@ -498,7 +778,7 @@
[(t locale "workspace.assets.edit") handle-edit-typography-clicked]
[(t locale "workspace.assets.delete") handle-delete-typography]]}]
(when open?
[:div.group-list
[:div.asset-list
(for [typography (sort-by :ts typographies)]
[:& typography-entry
{:key (:id typography)
@ -511,6 +791,9 @@
:editting? (= editting-id (:id typography))
:focus-name? (= (:rename-typography local) (:id typography))}])])]))
;; --- Assets toolbox ----
(defn file-colors-ref
[id]
(l/derived (fn [state]
@ -656,8 +939,8 @@
:open? (open-box? :typographies)}])
(when (and (not show-components?) (not show-graphics?) (not show-colors?))
[:div.asset-group
[:div.group-title (t locale "workspace.assets.not-found")]])]))]))
[:div.asset-section
[:div.asset-title (t locale "workspace.assets.not-found")]])]))]))
(mf/defc assets-toolbox

View file

@ -780,6 +780,10 @@ msgstr "Confirm password"
msgid "labels.content"
msgstr "Content"
#: src/app/main/ui/workspace/sidebar/assets.cljs
msgid "labels.create"
msgstr "Create"
#: src/app/main/ui/dashboard/team_form.cljs, src/app/main/ui/dashboard/team_form.cljs
msgid "labels.create-team"
msgstr "Create new team"
@ -1439,6 +1443,22 @@ msgstr "File library"
msgid "workspace.assets.graphics"
msgstr "Graphics"
#: src/app/main/ui/workspace/sidebar/assets.cljs
msgid "workspace.assets.group"
msgstr "Group"
#: src/app/main/ui/workspace/sidebar/assets.cljs
msgid "workspace.assets.create-group"
msgstr "Create a group"
#: src/app/main/ui/workspace/sidebar/assets.cljs
msgid "workspace.assets.group-name"
msgstr "Group name"
#: src/app/main/ui/workspace/sidebar/assets.cljs
msgid "workspace.assets.create-group-hint"
msgstr "Your items are going to be named automatically as \"group name / item name\""
#: src/app/main/ui/workspace/sidebar/assets.cljs
msgid "workspace.assets.libraries"
msgstr "Libraries"
@ -2437,4 +2457,4 @@ 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

@ -776,6 +776,10 @@ msgstr "Confirmar contraseña"
msgid "labels.content"
msgstr "Contenido"
#: src/app/main/ui/workspace/sidebar/assets.cljs
msgid "labels.create"
msgstr "Crear"
#: src/app/main/ui/dashboard/team_form.cljs, src/app/main/ui/dashboard/team_form.cljs
msgid "labels.create-team"
msgstr "Crea un nuevo equipo"
@ -1419,6 +1423,22 @@ msgstr "Biblioteca del archivo"
msgid "workspace.assets.graphics"
msgstr "Gráficos"
#: src/app/main/ui/workspace/sidebar/assets.cljs
msgid "workspace.assets.group"
msgstr "Agrupar"
#: src/app/main/ui/workspace/sidebar/assets.cljs
msgid "workspace.assets.create-group"
msgstr "Crear un grupo"
#: src/app/main/ui/workspace/sidebar/assets.cljs
msgid "workspace.assets.group-name"
msgstr "Nombre del grupo"
#: src/app/main/ui/workspace/sidebar/assets.cljs
msgid "workspace.assets.create-group-hint"
msgstr "Tus elementos se renombrarán automáticamente a \"nombre grupo / nombre elemento\""
#: src/app/main/ui/workspace/sidebar/assets.cljs
msgid "workspace.assets.libraries"
msgstr "Bibliotecas"
@ -2419,4 +2439,4 @@ msgid "workspace.updates.update"
msgstr "Actualizar"
msgid "workspace.viewport.click-to-close-path"
msgstr "Pulsar para cerrar la ruta"
msgstr "Pulsar para cerrar la ruta"