mirror of
https://github.com/penpot/penpot.git
synced 2025-06-07 22:51:38 +02:00
⚡ Refactor state management of workspace assets sidebar
This commit is contained in:
parent
ef27301238
commit
fcc4f4eed8
16 changed files with 1665 additions and 1368 deletions
|
@ -7,13 +7,13 @@
|
|||
(ns app.main.ui.workspace.libraries
|
||||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.data.macros :as dm]
|
||||
[app.common.types.components-list :as ctkl]
|
||||
[app.main.data.dashboard :as dd]
|
||||
[app.main.data.modal :as modal]
|
||||
[app.main.data.workspace.libraries :as dwl]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.icons :as i] [app.main.ui.workspace.sidebar.assets :as a]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [tr]]
|
||||
[app.util.keyboard :as kbd]
|
||||
|
@ -22,243 +22,302 @@
|
|||
[okulary.core :as l]
|
||||
[rumext.v2 :as mf]))
|
||||
|
||||
(def workspace-file
|
||||
(def ref:workspace-file
|
||||
(l/derived :workspace-file st/state))
|
||||
|
||||
(defn library-str
|
||||
(defn create-file-library-ref
|
||||
[library-id]
|
||||
(letfn [(getter-fn [state]
|
||||
(let [fdata (let [{:keys [id] :as wfile} (:workspace-data state)]
|
||||
(if (= id library-id)
|
||||
wfile
|
||||
(dm/get-in state [:workspace-libraries library-id :data])))]
|
||||
{:colors (-> fdata :colors vals)
|
||||
:media (-> fdata :media vals)
|
||||
:components (ctkl/components-seq fdata)
|
||||
:typographies (-> fdata :typographies vals)}))]
|
||||
(l/derived getter-fn st/state =)))
|
||||
|
||||
(defn- describe-library
|
||||
[components-count graphics-count colors-count typography-count]
|
||||
(str
|
||||
(str/join " · "
|
||||
(cond-> []
|
||||
(< 0 components-count)
|
||||
(pos? components-count)
|
||||
(conj (tr "workspace.libraries.components" components-count))
|
||||
|
||||
(< 0 graphics-count)
|
||||
(pos? graphics-count)
|
||||
(conj (tr "workspace.libraries.graphics" graphics-count))
|
||||
|
||||
(< 0 colors-count)
|
||||
(pos? colors-count)
|
||||
(conj (tr "workspace.libraries.colors" colors-count))
|
||||
|
||||
(< 0 typography-count)
|
||||
(pos? typography-count)
|
||||
(conj (tr "workspace.libraries.typography" typography-count))))
|
||||
"\u00A0"))
|
||||
|
||||
(defn local-library-str
|
||||
(defn- describe-linked-library
|
||||
[library]
|
||||
(let [components-count (count (or (ctkl/components-seq (:data library)) []))
|
||||
graphics-count (count (get-in library [:data :media] []))
|
||||
colors-count (count (get-in library [:data :colors] []))
|
||||
typography-count (count (get-in library [:data :typographies] []))]
|
||||
(library-str components-count graphics-count colors-count typography-count)))
|
||||
graphics-count (count (dm/get-in library [:data :media] []))
|
||||
colors-count (count (dm/get-in library [:data :colors] []))
|
||||
typography-count (count (dm/get-in library [:data :typographies] []))]
|
||||
(describe-library components-count graphics-count colors-count typography-count)))
|
||||
|
||||
(defn external-library-str
|
||||
(defn- describe-external-library
|
||||
[library]
|
||||
(let [components-count (get-in library [:library-summary :components :count] 0)
|
||||
graphics-count (get-in library [:library-summary :media :count] 0)
|
||||
colors-count (get-in library [:library-summary :colors :count] 0)
|
||||
typography-count (get-in library [:library-summary :typographies :count] 0)]
|
||||
(library-str components-count graphics-count colors-count typography-count)))
|
||||
(let [components-count (dm/get-in library [:library-summary :components :count] 0)
|
||||
graphics-count (dm/get-in library [:library-summary :media :count] 0)
|
||||
colors-count (dm/get-in library [:library-summary :colors :count] 0)
|
||||
typography-count (dm/get-in library [:library-summary :typographies :count] 0)]
|
||||
(describe-library components-count graphics-count colors-count typography-count)))
|
||||
|
||||
(mf/defc libraries-tab
|
||||
[{:keys [file colors typographies media components libraries shared-files] :as props}]
|
||||
(let [search-term (mf/use-state "")
|
||||
{::mf/wrap-props false}
|
||||
[{:keys [file-id shared? linked-libraries shared-libraries]}]
|
||||
(let [search-term* (mf/use-state "")
|
||||
search-term (deref search-term*)
|
||||
|
||||
sorted-libraries (->> (vals libraries)
|
||||
(sort-by #(str/lower (:name %))))
|
||||
library-ref (mf/with-memo [file-id]
|
||||
(create-file-library-ref file-id))
|
||||
library (deref library-ref)
|
||||
colors (:colors library)
|
||||
components (:components library)
|
||||
media (:media library)
|
||||
typographies (:typographies library)
|
||||
|
||||
filtered-files (->> shared-files
|
||||
(filter #(not= (:id %) (:id file)))
|
||||
(filter #(nil? (get libraries (:id %))))
|
||||
(filter #(matches-search (:name %) @search-term))
|
||||
(sort-by #(str/lower (:name %))))
|
||||
shared-libraries
|
||||
(mf/with-memo [shared-libraries linked-libraries file-id search-term]
|
||||
(->> shared-libraries
|
||||
(remove #(= (:id %) file-id))
|
||||
(remove #(contains? linked-libraries (:id %)))
|
||||
(filter #(matches-search (:name %) search-term))
|
||||
(sort-by (comp str/lower :name))))
|
||||
|
||||
on-search-term-change
|
||||
(mf/use-callback
|
||||
linked-libraries
|
||||
(mf/with-memo [linked-libraries]
|
||||
(->> (vals linked-libraries)
|
||||
(sort-by (comp str/lower :name))))
|
||||
|
||||
change-search-term
|
||||
(mf/use-fn
|
||||
(fn [event]
|
||||
(let [value (-> (dom/get-target event)
|
||||
(dom/get-value))]
|
||||
(reset! search-term value))))
|
||||
(reset! search-term* value))))
|
||||
|
||||
on-search-clear
|
||||
(mf/use-callback
|
||||
(fn [_]
|
||||
(reset! search-term "")))
|
||||
clear-search-term
|
||||
(mf/use-fn #(reset! search-term* ""))
|
||||
|
||||
link-library
|
||||
(mf/use-callback (mf/deps file) #(st/emit! (dwl/link-file-to-library (:id file) %)))
|
||||
(mf/use-fn
|
||||
(mf/deps file-id)
|
||||
(fn [event]
|
||||
(let [library-id (some-> (dom/get-target event)
|
||||
(dom/get-data "library-id")
|
||||
(parse-uuid))]
|
||||
(st/emit! (dwl/link-file-to-library file-id library-id)))))
|
||||
|
||||
unlink-library
|
||||
(mf/use-callback
|
||||
(mf/deps file)
|
||||
(fn [library-id]
|
||||
(st/emit! (dwl/unlink-file-from-library (:id file) library-id)
|
||||
(dwl/sync-file (:id file) library-id))))
|
||||
add-shared
|
||||
(mf/use-callback
|
||||
(mf/deps file)
|
||||
#(st/emit! (dwl/set-file-shared (:id file) true)))
|
||||
|
||||
del-shared
|
||||
(mf/use-callback
|
||||
(mf/deps file)
|
||||
(fn [_]
|
||||
(st/emit! (dd/fetch-libraries-using-files [file]))
|
||||
(st/emit! (modal/show
|
||||
{:type :delete-shared
|
||||
:origin :unpublish
|
||||
:on-accept (fn []
|
||||
(st/emit! (dwl/set-file-shared (:id file) false))
|
||||
(modal/show! :libraries-dialog {}))
|
||||
:on-cancel #(modal/show! :libraries-dialog {})
|
||||
:count-libraries 1}))))
|
||||
handle-key-down
|
||||
(mf/use-callback
|
||||
(mf/use-fn
|
||||
(mf/deps file-id)
|
||||
(fn [event]
|
||||
(let [enter? (kbd/enter? event)
|
||||
esc? (kbd/esc? event)
|
||||
input-node (dom/event->target event)]
|
||||
(let [library-id (some-> (dom/get-target event)
|
||||
(dom/get-data "library-id")
|
||||
(parse-uuid))]
|
||||
(st/emit! (dwl/unlink-file-from-library file-id library-id)
|
||||
(dwl/sync-file file-id library-id)))))
|
||||
|
||||
(when enter?
|
||||
on-delete-accept
|
||||
(mf/use-fn
|
||||
(mf/deps file-id)
|
||||
#(st/emit! (dwl/set-file-shared file-id false)
|
||||
(modal/show :libraries-dialog {})))
|
||||
|
||||
on-delete-cancel
|
||||
(mf/use-fn #(st/emit! (modal/show :libraries-dialog {})))
|
||||
|
||||
publish
|
||||
(mf/use-fn
|
||||
(mf/deps file-id)
|
||||
#(st/emit! (dwl/set-file-shared file-id true)))
|
||||
|
||||
unpublish
|
||||
(mf/use-fn
|
||||
(mf/deps file-id)
|
||||
(fn [_]
|
||||
(st/emit! (modal/show
|
||||
{:type :delete-shared-libraries
|
||||
:ids #{file-id}
|
||||
:origin :unpublish
|
||||
:on-accept on-delete-accept
|
||||
:on-cancel on-delete-cancel
|
||||
:count-libraries 1}))))
|
||||
|
||||
handle-key-down
|
||||
(mf/use-fn
|
||||
(fn [event]
|
||||
(let [enter? (kbd/enter? event)
|
||||
esc? (kbd/esc? event)
|
||||
input-node (dom/event->target event)]
|
||||
(when ^boolean enter?
|
||||
(dom/blur! input-node))
|
||||
(when esc?
|
||||
(when ^boolean esc?
|
||||
(dom/blur! input-node)))))]
|
||||
|
||||
[:*
|
||||
[:div.section
|
||||
[:div.section-title (tr "workspace.libraries.in-this-file")]
|
||||
[:div.section-list
|
||||
|
||||
[:div.section-list-item
|
||||
[:div
|
||||
[:div {:data-kaka "1"}
|
||||
[:div.item-name (tr "workspace.libraries.file-library")]
|
||||
[:div.item-contents (library-str (count components) (count media) (count colors) (count typographies) )]]
|
||||
[:div.item-contents (describe-library
|
||||
(count components)
|
||||
(count media)
|
||||
(count colors)
|
||||
(count typographies))]]
|
||||
[:div
|
||||
(if (:is-shared file)
|
||||
(if ^boolean shared?
|
||||
[:input.item-button {:type "button"
|
||||
:value (tr "common.unpublish")
|
||||
:on-click del-shared}]
|
||||
:on-click unpublish}]
|
||||
[:input.item-button {:type "button"
|
||||
:value (tr "common.publish")
|
||||
:on-click add-shared}])]]
|
||||
:on-click publish}])]]
|
||||
|
||||
(for [library sorted-libraries]
|
||||
[:div.section-list-item {:key (:id library)}
|
||||
[:div.item-name (:name library)]
|
||||
[:div.item-contents (local-library-str library)]
|
||||
(for [{:keys [id name] :as library} linked-libraries]
|
||||
[:div.section-list-item {:key (dm/str id)}
|
||||
[:div.item-name name]
|
||||
[:div.item-contents (describe-linked-library library)]
|
||||
[:input.item-button {:type "button"
|
||||
:value (tr "labels.remove")
|
||||
:on-click #(unlink-library (:id library))}]])
|
||||
]]
|
||||
:data-library-id (dm/str id)
|
||||
:on-click unlink-library}]])]]
|
||||
|
||||
[:div.section
|
||||
[:div.section-title (tr "workspace.libraries.shared-libraries")]
|
||||
[:div.libraries-search
|
||||
[:input.search-input
|
||||
{:placeholder (tr "workspace.libraries.search-shared-libraries")
|
||||
:type "text"
|
||||
:value @search-term
|
||||
:on-change on-search-term-change
|
||||
:value search-term
|
||||
:on-change change-search-term
|
||||
:on-key-down handle-key-down}]
|
||||
(if (str/empty? @search-term)
|
||||
(if (str/empty? search-term)
|
||||
[:div.search-icon
|
||||
i/search]
|
||||
[:div.search-icon.search-close
|
||||
{:on-click on-search-clear}
|
||||
{:on-click clear-search-term}
|
||||
i/close])]
|
||||
(if (> (count filtered-files) 0)
|
||||
|
||||
(if (seq shared-libraries)
|
||||
[:div.section-list
|
||||
(for [file filtered-files]
|
||||
[:div.section-list-item {:key (:id file)}
|
||||
[:div.item-name (:name file)]
|
||||
[:div.item-contents (external-library-str file)]
|
||||
(for [{:keys [id name] :as library} shared-libraries]
|
||||
[:div.section-list-item {:key (dm/str id)}
|
||||
[:div.item-name name]
|
||||
[:div.item-contents (describe-external-library library)]
|
||||
[:input.item-button {:type "button"
|
||||
:value (tr "workspace.libraries.add")
|
||||
:on-click #(link-library (:id file))}]])]
|
||||
:data-library-id (dm/str id)
|
||||
:on-click link-library}]])]
|
||||
|
||||
[:div.section-list-empty
|
||||
(if (nil? shared-files)
|
||||
(if (nil? shared-libraries)
|
||||
i/loader-pencil
|
||||
[:* i/library
|
||||
(if (str/empty? @search-term)
|
||||
(if (str/empty? search-term)
|
||||
(tr "workspace.libraries.no-shared-libraries-available")
|
||||
(tr "workspace.libraries.no-matches-for" @search-term))])])]]))
|
||||
(tr "workspace.libraries.no-matches-for" search-term))])])]]))
|
||||
|
||||
|
||||
(mf/defc updates-tab
|
||||
[{:keys [file libraries] :as props}]
|
||||
(let [libraries-need-sync (filter #(> (:modified-at %) (:synced-at %))
|
||||
(vals libraries))
|
||||
update-library #(st/emit! (dwl/sync-file (:id file) %))]
|
||||
[:div.section
|
||||
(if (empty? libraries-need-sync)
|
||||
[:div.section-list-empty
|
||||
i/library
|
||||
(tr "workspace.libraries.no-libraries-need-sync")]
|
||||
[:*
|
||||
[:div.section-title (tr "workspace.libraries.library")]
|
||||
[:div.section-list
|
||||
(for [library libraries-need-sync]
|
||||
[:div.section-list-item {:key (:id library)}
|
||||
[:div.item-name (:name library)]
|
||||
[:div.item-contents (external-library-str library)]
|
||||
[:input.item-button {:type "button"
|
||||
:value (tr "workspace.libraries.update")
|
||||
:on-click #(update-library (:id library))}]])]])]))
|
||||
{::mf/wrap-props false}
|
||||
[{:keys [file-id libraries]}]
|
||||
(let [libraries (mf/with-memo [libraries]
|
||||
(filter #(> (:modified-at %) (:synced-at %)) (vals libraries)))
|
||||
|
||||
update (mf/use-fn
|
||||
(mf/deps file-id)
|
||||
(fn [event]
|
||||
(let [library-id (some-> (dom/get-target event)
|
||||
(dom/get-data "library-id")
|
||||
(parse-uuid))]
|
||||
(st/emit! (dwl/sync-file file-id library-id)))))]
|
||||
[:div.section
|
||||
(if (empty? libraries)
|
||||
[:div.section-list-empty
|
||||
i/library
|
||||
(tr "workspace.libraries.no-libraries-need-sync")]
|
||||
[:*
|
||||
[:div.section-title (tr "workspace.libraries.library")]
|
||||
[:div.section-list
|
||||
(for [{:keys [id name] :as library} libraries]
|
||||
[:div.section-list-item {:key (dm/str id)}
|
||||
[:div.item-name name]
|
||||
[:div.item-contents (describe-external-library library)]
|
||||
[:input.item-button {:type "button"
|
||||
:value (tr "workspace.libraries.update")
|
||||
:data-library-id (dm/str id)
|
||||
:on-click update}]])]])]))
|
||||
|
||||
(mf/defc libraries-dialog
|
||||
{::mf/register modal/components
|
||||
::mf/register-as :libraries-dialog}
|
||||
[{:keys [] :as ctx}]
|
||||
(let [selected-tab (mf/use-state :libraries)
|
||||
project (mf/deref refs/workspace-project)
|
||||
file (mf/deref workspace-file)
|
||||
[]
|
||||
(let [project (mf/deref refs/workspace-project)
|
||||
file (mf/deref ref:workspace-file)
|
||||
|
||||
libraries (->> (mf/deref refs/workspace-libraries)
|
||||
(d/removem (fn [[_ val]] (:is-indirect val))))
|
||||
shared-files (mf/deref refs/workspace-shared-files)
|
||||
team-id (:team-id project)
|
||||
file-id (:id file)
|
||||
shared? (:is-shared file)
|
||||
|
||||
colors-ref (mf/use-memo (mf/deps (:id file)) #(a/file-colors-ref (:id file)))
|
||||
colors (mf/deref colors-ref)
|
||||
selected-tab* (mf/use-state :libraries)
|
||||
selected-tab (deref selected-tab*)
|
||||
|
||||
typography-ref (mf/use-memo (mf/deps (:id file)) #(a/file-typography-ref (:id file)))
|
||||
typographies (mf/deref typography-ref)
|
||||
libraries (mf/deref refs/workspace-libraries)
|
||||
libraries (mf/with-memo [libraries]
|
||||
(d/removem (fn [[_ val]] (:is-indirect val)) libraries))
|
||||
|
||||
media-ref (mf/use-memo (mf/deps (:id file)) #(a/file-media-ref (:id file)))
|
||||
media (mf/deref media-ref)
|
||||
;; NOTE: we really don't need react on shared files
|
||||
shared-libraries
|
||||
(mf/deref refs/workspace-shared-files)
|
||||
|
||||
components-ref (mf/use-memo (mf/deps (:id file)) #(a/file-components-ref (:id file)))
|
||||
components (mf/deref components-ref)
|
||||
select-libraries-tab
|
||||
(mf/use-fn #(reset! selected-tab* :libraries))
|
||||
|
||||
change-tab #(reset! selected-tab %)
|
||||
close #(modal/hide!)]
|
||||
select-updates-tab
|
||||
(mf/use-fn #(reset! selected-tab* :updates))
|
||||
|
||||
(mf/use-effect
|
||||
(mf/deps project)
|
||||
(fn []
|
||||
(when (:team-id project)
|
||||
(st/emit! (dwl/fetch-shared-files {:team-id (:team-id project)})))))
|
||||
close-dialog
|
||||
(mf/use-fn #(modal/hide!))]
|
||||
|
||||
(mf/with-effect [team-id]
|
||||
(when team-id
|
||||
(st/emit! (dwl/fetch-shared-files {:team-id team-id}))))
|
||||
|
||||
[:div.modal-overlay
|
||||
[:div.modal.libraries-dialog
|
||||
[:a.close {:on-click close} i/close]
|
||||
[:a.close {:on-click close-dialog} i/close]
|
||||
[:div.modal-content
|
||||
[:div.libraries-header
|
||||
[:div.header-item
|
||||
{:class (dom/classnames :active (= @selected-tab :libraries))
|
||||
:on-click #(change-tab :libraries)}
|
||||
{:class (dom/classnames :active (= selected-tab :libraries))
|
||||
:on-click select-libraries-tab}
|
||||
(tr "workspace.libraries.libraries")]
|
||||
[:div.header-item
|
||||
{:class (dom/classnames :active (= @selected-tab :updates))
|
||||
:on-click #(change-tab :updates)}
|
||||
{:class (dom/classnames :active (= selected-tab :updates))
|
||||
:on-click select-updates-tab}
|
||||
(tr "workspace.libraries.updates")]]
|
||||
[:div.libraries-content
|
||||
(case @selected-tab
|
||||
(case selected-tab
|
||||
:libraries
|
||||
[:& libraries-tab {:file file
|
||||
:colors colors
|
||||
:typographies typographies
|
||||
:media media
|
||||
:components components
|
||||
:libraries libraries
|
||||
:shared-files shared-files}]
|
||||
[:& libraries-tab {:file-id file-id
|
||||
:shared? shared?
|
||||
:linked-libraries libraries
|
||||
:shared-libraries shared-libraries}]
|
||||
:updates
|
||||
[:& updates-tab {:file file
|
||||
[:& updates-tab {:file-id file-id
|
||||
:libraries libraries}])]]]]))
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue