mirror of
https://github.com/penpot/penpot.git
synced 2025-04-01 09:31:26 -05:00
Merge pull request #808 from penpot/group-assets
🎉 Group items in assets sidebar
This commit is contained in:
commit
5a49ce2028
19 changed files with 727 additions and 146 deletions
|
@ -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.
|
||||
|
||||
|
||||
|
|
|
@ -62,6 +62,10 @@
|
|||
(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)
|
||||
(d/export helpers/compact-name)
|
||||
|
||||
;; Process changes
|
||||
(d/export changes/process-changes)
|
||||
|
|
|
@ -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]}]
|
||||
|
|
|
@ -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,55 @@
|
|||
[parent-idx _] (d/seek (fn [[idx child-id]] (= child-id shape-id))
|
||||
(d/enumerate (:shapes parent)))]
|
||||
parent-idx))
|
||||
|
||||
(defn split-path
|
||||
[path]
|
||||
"Decompose a string in the form 'one / two / three' into
|
||||
an array of strings, normalizing spaces."
|
||||
(->> (str/split path "/")
|
||||
(map str/trim)
|
||||
(remove str/empty?)))
|
||||
|
||||
(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 (split-path path-name)
|
||||
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 item 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 (split-path path)
|
||||
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]))))
|
||||
|
||||
(defn compact-name
|
||||
"Append the first item of the path and the name."
|
||||
[path name]
|
||||
(let [path-split (split-path path)]
|
||||
(merge-path-item (first path-split) name)))
|
||||
|
||||
|
|
|
@ -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]
|
||||
|
|
3
frontend/resources/images/icons/listing-enum.svg
Normal file
3
frontend/resources/images/icons/listing-enum.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500">
|
||||
<path d="M37.5 437.5v25h425v-25zM25 400a25 25 0 00-25 25v50a25 25 0 0025 25h450a25 25 0 0025-25v-50a25 25 0 00-25-25zM37.5 37.5v25h425v-25zM25 0A25 25 0 000 25v50a25 25 0 0025 25h450a25 25 0 0025-25V25a25 25 0 00-25-25zm12.5 237.5v25h425v-25zM25 200a25 25 0 00-25 25v50a25 25 0 0025 25h450a25 25 0 0025-25v-50a25 25 0 00-25-25z" clip-rule="evenodd" fill-rule="evenodd"/>
|
||||
</svg>
|
After Width: | Height: | Size: 446 B |
3
frontend/resources/images/icons/listing-thumbs.svg
Normal file
3
frontend/resources/images/icons/listing-thumbs.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500">
|
||||
<path d="M37.5 37.5v150h150v-150zM25 0A25 25 0 000 25v175a25 25 0 0025 25h175a25 25 0 0025-25V25a25 25 0 00-25-25zm287.5 37.5v150h150v-150zM300 0a25 25 0 00-25 25v175a25 25 0 0025 25h175a25 25 0 0025-25V25a25 25 0 00-25-25zm12.5 312.5v150h150v-150zM300 275a25 25 0 00-25 25v175a25 25 0 0025 25h175a25 25 0 0025-25V300a25 25 0 00-25-25zM37.5 312.5v150h150v-150zM25 275a25 25 0 00-25 25v175a25 25 0 0025 25h175a25 25 0 0025-25V300a25 25 0 00-25-25z" clip-rule="evenodd" fill-rule="evenodd"/>
|
||||
</svg>
|
After Width: | Height: | Size: 565 B |
3
frontend/resources/images/icons/sort-ascending.svg
Normal file
3
frontend/resources/images/icons/sort-ascending.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500">
|
||||
<path d="M169.748 283.25a25.007 23.438 0 0120.24 37.219L85.49 455.125h84.257a25.007 23.438 0 010 46.875H36.378a25.007 23.438 0 01-20.24-37.219l104.496-134.656H36.378a25.007 23.438 0 010-46.875zM353.133 2a25.007 23.438 0 0125.007 23.438v399.187l82.956-72.562a25.007 23.438 0 0134.143 34.25L370.204 495.688a25.007 23.438 0 01-34.143 0L211.026 386.313a25.007 23.438 0 0134.143-34.25l82.957 72.562V25.438A25.007 23.438 0 01353.133 2zm-250.07 0a25.007 23.438 0 0123.64 15.781l75.02 203.125a25.027 23.457 0 01-47.312 15.313l-11.437-31.094H63.152l-11.437 31.094A25.043 23.471 0 014.37 220.906L79.423 17.781A25.007 23.438 0 01103.063 2zm0 95.094L80.49 158.25h45.18z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 736 B |
3
frontend/resources/images/icons/sort-descending.svg
Normal file
3
frontend/resources/images/icons/sort-descending.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500">
|
||||
<path d="M353.133 2a25.007 23.436 0 0125.007 23.436v399.16l82.956-72.557a25.007 23.436 0 0134.143 34.248L370.204 495.654a25.007 23.436 0 01-34.143 0L211.026 386.287a25.007 23.436 0 0134.143-34.248l82.957 72.557V25.436A25.007 23.436 0 01353.133 2zm-250.07 249.983a25.007 23.436 0 0123.64 15.78l75.02 203.112a25.027 23.455 0 11-47.312 15.311l-11.437-31.092H63.152l-11.437 31.092A25.043 23.47 0 114.37 470.875l75.054-203.112a25.007 23.436 0 0123.64-15.78zm0 95.087L80.49 408.223h45.18zM169.748 2a25.007 23.436 0 0120.24 37.216L85.49 173.863h84.257a25.007 23.436 0 110 46.872H36.378a25.007 23.436 0 01-20.24-37.216L120.635 48.872H36.378a25.007 23.436 0 010-46.872z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 739 B |
|
@ -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;
|
||||
|
|
|
@ -118,41 +118,89 @@
|
|||
cursor: pointer;
|
||||
}
|
||||
|
||||
.asset-group {
|
||||
.listing-options {
|
||||
background-color: $color-gray-60;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
padding: $medium $small 0 $small;
|
||||
|
||||
.listing-option-btn {
|
||||
cursor: pointer;
|
||||
margin-left: $small;
|
||||
|
||||
svg {
|
||||
fill: $color-gray-20;
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.asset-section {
|
||||
background-color: $color-gray-60;
|
||||
border-top: 1px solid $color-gray-50;
|
||||
padding: $small;
|
||||
font-size: $fs12;
|
||||
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;
|
||||
// First child is the listing options buttons
|
||||
&:not(:nth-child(2)) {
|
||||
border-top: 1px solid $color-gray-50;
|
||||
}
|
||||
|
||||
.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 +215,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 +243,7 @@
|
|||
.grid-cell {
|
||||
background-color: $color-canvas;
|
||||
border-radius: 4px;
|
||||
border: 2px solid transparent;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
@ -242,26 +294,80 @@
|
|||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.asset-title + .asset-enum {
|
||||
margin-top: $small;
|
||||
}
|
||||
|
||||
.asset-enum {
|
||||
.enum-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: $small;
|
||||
cursor: pointer;
|
||||
|
||||
& > svg,
|
||||
& > img {
|
||||
background-color: $color-canvas;
|
||||
border-radius: 4px;
|
||||
border: 2px solid transparent;
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
margin-right: $small;
|
||||
}
|
||||
|
||||
.item-name {
|
||||
width: calc(100% - 24px - #{$small});
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
display: block;
|
||||
|
||||
&.editing {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.editable-label-input {
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.editable-label-close {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.enum-item:hover,
|
||||
.enum-item.selected,
|
||||
{
|
||||
color: $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;
|
||||
|
|
|
@ -983,7 +983,7 @@
|
|||
display: flex;
|
||||
}
|
||||
|
||||
.asset-group {
|
||||
.asset-section {
|
||||
.typography-entry {
|
||||
margin: 0.25rem 0;
|
||||
}
|
||||
|
|
|
@ -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}))))))
|
||||
|
||||
|
@ -190,7 +193,7 @@
|
|||
ptk/WatchEvent
|
||||
(watch [_ state s]
|
||||
(let [rchg {:type :add-typography
|
||||
:typography (assoc typography :ts (.now js/Date))}
|
||||
:typography typography}
|
||||
uchg {:type :del-typography
|
||||
:id (:id typography)}]
|
||||
(rx/of (dwc/commit-changes [rchg] [uchg] {: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
|
||||
|
@ -310,6 +318,7 @@
|
|||
uchanges [{:type :add-component
|
||||
:id id
|
||||
:name (:name component)
|
||||
:path (:path component)
|
||||
:shapes (vals (:objects component))}]]
|
||||
|
||||
(rx/of (dwc/commit-changes rchanges uchanges {:commit-local? true}))))))
|
||||
|
|
|
@ -14,8 +14,10 @@
|
|||
[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)
|
||||
tooltip (get props :tooltip)
|
||||
input (mf/use-ref nil)
|
||||
state (mf/use-state (:editing false))
|
||||
is-editing (:editing @state)
|
||||
start-editing (fn []
|
||||
|
@ -53,4 +55,5 @@
|
|||
: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])))
|
||||
:title tooltip
|
||||
:on-double-click on-dbl-click} display-value])))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -60,6 +60,8 @@
|
|||
(def libraries (icon-xref :libraries))
|
||||
(def library (icon-xref :library))
|
||||
(def line (icon-xref :line))
|
||||
(def listing-enum (icon-xref :listing-enum))
|
||||
(def listing-thumbs (icon-xref :listing-thumbs))
|
||||
(def line-height (icon-xref :line-height))
|
||||
(def loader (icon-xref :loader))
|
||||
(def lock (icon-xref :lock))
|
||||
|
@ -117,6 +119,8 @@
|
|||
(def shape-vdistribute (icon-xref :shape-vdistribute))
|
||||
(def size-horiz (icon-xref :size-horiz))
|
||||
(def size-vert (icon-xref :size-vert))
|
||||
(def sort-ascending (icon-xref :sort-ascending))
|
||||
(def sort-descending (icon-xref :sort-descending))
|
||||
(def strikethrough (icon-xref :strikethrough))
|
||||
(def stroke (icon-xref :stroke))
|
||||
(def sublevel (icon-xref :sublevel))
|
||||
|
|
|
@ -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,28 +40,149 @@
|
|||
[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}]
|
||||
[{:keys [file-id local? components listing-thumbs? 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
|
||||
(mf/deps state)
|
||||
(st/emitf (dwl/duplicate-component {:id (:component-id @state)})))
|
||||
(fn []
|
||||
(if (empty? selected)
|
||||
(st/emit! (dwl/duplicate-component {:id (:component-id @state)}))
|
||||
(do
|
||||
(st/emit! (dwc/start-undo-transaction))
|
||||
(apply st/emit! (map #(dwl/duplicate-component {:id %}) selected))
|
||||
(st/emit! (dwc/commit-undo-transaction))))))
|
||||
|
||||
on-delete
|
||||
(mf/use-callback
|
||||
(mf/deps state)
|
||||
(fn []
|
||||
(st/emit! (dwl/delete-component {:id (:component-id @state)}))
|
||||
(if (empty? selected)
|
||||
(st/emit! (dwl/delete-component {:id (:component-id @state)}))
|
||||
(do
|
||||
(st/emit! (dwc/start-undo-transaction))
|
||||
(apply st/emit! (map #(dwl/delete-component {:id %}) selected))
|
||||
(st/emit! (dwc/commit-undo-transaction))))
|
||||
(st/emit! (dwl/sync-file file-id file-id))))
|
||||
|
||||
on-rename
|
||||
|
@ -94,6 +218,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 +279,60 @@
|
|||
: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 {:class-name (dom/classnames
|
||||
:asset-grid @listing-thumbs?
|
||||
:big @listing-thumbs?
|
||||
:asset-enum (not @listing-thumbs?))}
|
||||
(for [component components]
|
||||
(let [renaming? (= (:renaming @state)(:id component))]
|
||||
[:div {:key (:id component)
|
||||
:class-name (dom/classnames
|
||||
:selected (contains? selected (:id component))
|
||||
:grid-cell @listing-thumbs?
|
||||
:enum-item (not @listing-thumbs?))
|
||||
: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 @listing-thumbs?
|
||||
:item-name (not @listing-thumbs?)
|
||||
:editing renaming?)
|
||||
:value (cp/merge-path-item (:path component) (:name component))
|
||||
:tooltip (cp/merge-path-item (:path component) (:name component))
|
||||
:display-value (if @listing-thumbs?
|
||||
(:name component)
|
||||
(cp/compact-name (:path component)
|
||||
(:name component)))
|
||||
:editing? renaming?
|
||||
:disable-dbl-click? true
|
||||
:on-change do-rename
|
||||
:on-cancel cancel-rename}]]))])])))
|
||||
|
||||
(when local?
|
||||
[:& context-menu
|
||||
|
@ -133,18 +341,29 @@
|
|||
: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}]
|
||||
[{:keys [file-id local? objects listing-thumbs? open?] :as props}]
|
||||
(let [input-ref (mf/use-ref nil)
|
||||
state (mf/use-state {:menu-open false
|
||||
: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
|
||||
|
@ -164,8 +383,12 @@
|
|||
(mf/use-callback
|
||||
(mf/deps state)
|
||||
(fn []
|
||||
(let [params {:id (:object-id @state)}]
|
||||
(st/emit! (dwl/delete-media params)))))
|
||||
(if (empty? selected)
|
||||
(st/emit! (dwl/delete-media {:id (:object-id @state)}))
|
||||
(do
|
||||
(st/emit! (dwc/start-undo-transaction))
|
||||
(apply st/emit! (map #(dwl/delete-media {:id %}) selected))
|
||||
(st/emit! (dwc/commit-undo-transaction))))))
|
||||
|
||||
on-rename
|
||||
(mf/use-callback
|
||||
|
@ -200,6 +423,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 +485,82 @@
|
|||
(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 {:class-name (dom/classnames
|
||||
:asset-grid @listing-thumbs?
|
||||
:asset-enum (not @listing-thumbs?))}
|
||||
(for [object objects]
|
||||
[:div {:key (:id object)
|
||||
:class-name (dom/classnames
|
||||
:selected (contains? selected (:id object))
|
||||
:grid-cell @listing-thumbs?
|
||||
:enum-item (not @listing-thumbs?))
|
||||
: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}])])
|
||||
(let [renaming? (= (:renaming @state) (:id object))]
|
||||
[:& editable-label
|
||||
{:class-name (dom/classnames
|
||||
:cell-name @listing-thumbs?
|
||||
:item-name (not @listing-thumbs?)
|
||||
:editing renaming?)
|
||||
:value (cp/merge-path-item (:path object) (:name object))
|
||||
:tooltip (cp/merge-path-item (:path object) (:name object))
|
||||
:display-value (if @listing-thumbs?
|
||||
(:name object)
|
||||
(cp/compact-name (:path object)
|
||||
(: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 +647,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 +697,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 +717,9 @@
|
|||
:local? local?
|
||||
:locale locale}]))])]))
|
||||
|
||||
|
||||
;; ---- Typography box ----
|
||||
|
||||
(mf/defc typography-box
|
||||
[{:keys [file file-id local? typographies locale open?] :as props}]
|
||||
|
||||
|
@ -480,13 +793,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,8 +811,8 @@
|
|||
[(t locale "workspace.assets.edit") handle-edit-typography-clicked]
|
||||
[(t locale "workspace.assets.delete") handle-delete-typography]]}]
|
||||
(when open?
|
||||
[:div.group-list
|
||||
(for [typography (sort-by :ts typographies)]
|
||||
[:div.asset-list
|
||||
(for [typography typographies]
|
||||
[:& typography-entry
|
||||
{:key (:id typography)
|
||||
:typography typography
|
||||
|
@ -511,6 +824,9 @@
|
|||
:editting? (= editting-id (:id typography))
|
||||
:focus-name? (= (:rename-typography local) (:id typography))}])])]))
|
||||
|
||||
|
||||
;; --- Assets toolbox ----
|
||||
|
||||
(defn file-colors-ref
|
||||
[id]
|
||||
(l/derived (fn [state]
|
||||
|
@ -554,43 +870,58 @@
|
|||
(l/derived refs/workspace-local)))
|
||||
|
||||
(defn apply-filters
|
||||
[coll filters]
|
||||
(->> coll
|
||||
(filter (fn [item]
|
||||
(or (matches-search (:name item "!$!") (:term filters))
|
||||
(matches-search (:value item "!$!") (:term filters)))))
|
||||
(sort-by #(str/lower (:name %)))))
|
||||
[coll filters reverse-sort?]
|
||||
(let [comp-fn (if reverse-sort? > <)]
|
||||
(->> coll
|
||||
(filter (fn [item]
|
||||
(or (matches-search (:name item "!$!") (:term filters))
|
||||
(matches-search (:value item "!$!") (:term filters)))))
|
||||
(sort-by #(str/lower (:name %)) comp-fn))))
|
||||
|
||||
(mf/defc file-library
|
||||
[{:keys [file local? default-open? filters locale] :as props}]
|
||||
(let [open-file (mf/deref (open-file-ref (:id file)))
|
||||
open? (-> open-file
|
||||
:library
|
||||
(d/nilv default-open?))
|
||||
open-box? (fn [box]
|
||||
(-> open-file
|
||||
box
|
||||
(d/nilv true)))
|
||||
shared? (:is-shared file)
|
||||
router (mf/deref refs/router)
|
||||
toggle-open (st/emitf (dwl/set-assets-box-open (:id file) :library (not open?)))
|
||||
[{:keys [file local? default-open? filters locale] :as props}]
|
||||
(let [open-file (mf/deref (open-file-ref (:id file)))
|
||||
open? (-> open-file
|
||||
:library
|
||||
(d/nilv default-open?))
|
||||
open-box? (fn [box]
|
||||
(-> open-file
|
||||
box
|
||||
(d/nilv true)))
|
||||
shared? (:is-shared file)
|
||||
router (mf/deref refs/router)
|
||||
|
||||
url (rt/resolve router :workspace
|
||||
{:project-id (:project-id file)
|
||||
:file-id (:id file)}
|
||||
{:page-id (get-in file [:data :pages 0])})
|
||||
reverse-sort? (mf/use-state false)
|
||||
listing-thumbs? (mf/use-state true)
|
||||
|
||||
colors-ref (mf/use-memo (mf/deps (:id file)) #(file-colors-ref (:id file)))
|
||||
colors (apply-filters (mf/deref colors-ref) filters)
|
||||
toggle-open (st/emitf (dwl/set-assets-box-open (:id file) :library (not open?)))
|
||||
|
||||
typography-ref (mf/use-memo (mf/deps (:id file)) #(file-typography-ref (:id file)))
|
||||
typographies (apply-filters (mf/deref typography-ref) filters)
|
||||
url (rt/resolve router :workspace
|
||||
{:project-id (:project-id file)
|
||||
:file-id (:id file)}
|
||||
{:page-id (get-in file [:data :pages 0])})
|
||||
|
||||
media-ref (mf/use-memo (mf/deps (:id file)) #(file-media-ref (:id file)))
|
||||
media (apply-filters (mf/deref media-ref) filters)
|
||||
colors-ref (mf/use-memo (mf/deps (:id file)) #(file-colors-ref (:id file)))
|
||||
colors (apply-filters (mf/deref colors-ref) filters @reverse-sort?)
|
||||
|
||||
components-ref (mf/use-memo (mf/deps (:id file)) #(file-components-ref (:id file)))
|
||||
components (apply-filters (mf/deref components-ref) filters)]
|
||||
typography-ref (mf/use-memo (mf/deps (:id file)) #(file-typography-ref (:id file)))
|
||||
typographies (apply-filters (mf/deref typography-ref) filters @reverse-sort?)
|
||||
|
||||
media-ref (mf/use-memo (mf/deps (:id file)) #(file-media-ref (:id file)))
|
||||
media (apply-filters (mf/deref media-ref) filters @reverse-sort?)
|
||||
|
||||
components-ref (mf/use-memo (mf/deps (:id file)) #(file-components-ref (:id file)))
|
||||
components (apply-filters (mf/deref components-ref) filters @reverse-sort?)
|
||||
|
||||
toggle-sort
|
||||
(mf/use-callback
|
||||
(fn [event]
|
||||
(swap! reverse-sort? not)))
|
||||
|
||||
toggle-listing
|
||||
(mf/use-callback
|
||||
(fn [event]
|
||||
(swap! listing-thumbs? not)))]
|
||||
|
||||
[:div.tool-window
|
||||
[:div.tool-window-bar.library-bar
|
||||
|
@ -630,15 +961,26 @@
|
|||
(or (> (count typographies) 0)
|
||||
(str/empty? (:term filters))))]
|
||||
[:div.tool-window-content
|
||||
[:div.listing-options
|
||||
[:div.listing-option-btn {:on-click toggle-sort}
|
||||
(if @reverse-sort?
|
||||
i/sort-descending
|
||||
i/sort-ascending)]
|
||||
[:div.listing-option-btn {:on-click toggle-listing}
|
||||
(if @listing-thumbs?
|
||||
i/listing-thumbs
|
||||
i/listing-thumbs)]]
|
||||
(when show-components?
|
||||
[:& components-box {:file-id (:id file)
|
||||
:local? local?
|
||||
:components components
|
||||
:listing-thumbs? listing-thumbs?
|
||||
:open? (open-box? :components)}])
|
||||
(when show-graphics?
|
||||
[:& graphics-box {:file-id (:id file)
|
||||
:local? local?
|
||||
:objects media
|
||||
:listing-thumbs? listing-thumbs?
|
||||
:open? (open-box? :graphics)}])
|
||||
(when show-colors?
|
||||
[:& colors-box {:file-id (:id file)
|
||||
|
@ -656,8 +998,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
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Add table
Reference in a new issue