assets.cljs 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. (ns frontend.components.assets
  2. (:require
  3. [clojure.set :refer [difference]]
  4. [clojure.string :as string]
  5. [rum.core :as rum]
  6. [frontend.state :as state]
  7. [frontend.context.i18n :refer [t]]
  8. [frontend.util :as util]
  9. [electron.ipc :as ipc]
  10. [promesa.core :as p]
  11. [medley.core :as medley]
  12. [frontend.ui :as ui]
  13. [frontend.config :as config]
  14. [frontend.components.select :as cp-select]
  15. [frontend.handler.notification :as notification]
  16. [frontend.handler.assets :as assets-handler]))
  17. (defn -get-all-formats
  18. []
  19. (->> (concat config/doc-formats
  20. config/audio-formats
  21. config/video-formats
  22. config/image-formats
  23. config/markup-formats)
  24. (map #(hash-map :id % :value (name %)))))
  25. (rum/defc input-auto-complete
  26. [{:keys [items item-cp class
  27. on-chosen on-keydown input-opts]}]
  28. (let [[*input-val, set-*input-val] (rum/use-state (atom nil))
  29. [input-empty?, set-input-empty?] (rum/use-state true)]
  30. (rum/use-effect!
  31. #(set-input-empty? (string/blank? @*input-val))
  32. [@*input-val])
  33. (cp-select/select
  34. {:items items
  35. :close-modal? false
  36. :item-cp item-cp
  37. :on-chosen on-chosen
  38. :on-input #(set-input-empty? (string/blank? %))
  39. :tap-*input-val #(set-*input-val %)
  40. :transform-fn (fn [results]
  41. (if (and *input-val
  42. (not (string/blank? @*input-val))
  43. (not (seq results)))
  44. [{:id nil :value @*input-val}]
  45. results))
  46. :host-opts {:class (util/classnames [:cp__input-ac class {:is-empty-input input-empty?}])}
  47. :input-opts (cond-> input-opts
  48. (fn? on-keydown)
  49. (assoc :on-key-down #(on-keydown % *input-val)))})))
  50. (rum/defc confirm-dir-with-alias-name
  51. [dir set-dir!]
  52. (let [[val set-val!] (rum/use-state "")
  53. on-submit (fn []
  54. (when-not (string/blank? val)
  55. (if-not (assets-handler/get-alias-by-name val)
  56. (do (set-dir! val dir nil)
  57. (state/close-modal!))
  58. (notification/show!
  59. (util/format "Alias name of [%s] already exists!" val) :warning))))]
  60. [:div.cp__assets-alias-name-content
  61. [:h1.text-2xl.opacity-90.mb-6 "What's the alias name of this selected directory?"]
  62. [:p [:strong "Directory path:"]
  63. [:a {:on-click #(when (util/electron?)
  64. (js/apis.openPath dir))} dir]]
  65. [:p [:strong "Alias name:"]
  66. [:input.px-1.border.rounded
  67. {:autoFocus true
  68. :value val
  69. :placeholder "eg. Books"
  70. :on-change (fn [^js e]
  71. (set-val! (util/trim-safe (.. e -target -value))))
  72. :on-key-up (fn [^js e]
  73. (when (and (= 13 (.-which e))
  74. (not (string/blank? val)))
  75. (on-submit)))}]]
  76. [:div.pt-6.flex.justify-end
  77. (ui/button
  78. "Save"
  79. :disabled (string/blank? val)
  80. :on-click on-submit)]]))
  81. (rum/defc restart-button
  82. []
  83. (ui/button (t :plugin/restart)
  84. :on-click #(js/logseq.api.relaunch)
  85. :small? true :intent "logseq"))
  86. (rum/defcs ^:large-vars/data-var alias-directories
  87. < rum/reactive
  88. (rum/local nil ::ext-editing-dir)
  89. [_state]
  90. (let [*ext-editing-dir (::ext-editing-dir _state)
  91. directories (into [] (state/sub :assets/alias-dirs))
  92. pick-exist assets-handler/get-alias-by-dir
  93. set-dir! (fn [name dir exts]
  94. (when (and name dir)
  95. (state/set-assets-alias-dirs!
  96. (let [exist (pick-exist dir)]
  97. (if exist
  98. (assoc directories (first exist) {:name name :dir dir :exts (set exts)})
  99. (conj directories {:dir dir :name name :exts (set exts)}))))))
  100. rm-dir (fn [dir]
  101. (when-let [exist (pick-exist dir)]
  102. (state/set-assets-alias-dirs!
  103. (medley/remove-nth (first exist) directories))))
  104. del-ext (fn [dir ext]
  105. (when-let [exist (and ext (pick-exist dir))]
  106. (let [exts (:exts (second exist))
  107. exts (difference exts (hash-set ext))
  108. name (:name (second exist))]
  109. (set-dir! name dir exts))))
  110. add-ext (fn [dir ext]
  111. (when-let [exist (and ext (pick-exist dir))]
  112. (let [exts (:exts (second exist))
  113. exts (conj exts (util/safe-lower-case ext))
  114. name (:name (second exist))]
  115. (set-dir! name dir exts))))
  116. confirm-dir (fn [dir set-dir!]
  117. (state/set-sub-modal!
  118. #(confirm-dir-with-alias-name dir set-dir!)))]
  119. [:div.cp__assets-alias-directories
  120. [:ul
  121. (for [{:keys [name dir exts]} directories]
  122. [:li.item.px-2.py-2
  123. [:div.flex.justify-between.items-center
  124. [:span.font-semibold
  125. (str "@" name)]
  126. [:div.flex.items-center.space-x-2
  127. [:a.opacity-90.active:opacity-50.text-sm.flex.space-x-1
  128. {:on-click #(when (util/electron?)
  129. (js/apis.openPath dir))}
  130. (ui/icon "folder") dir]]]
  131. [:div.flex.justify-between.items-center
  132. [:div.flex.mt-2.space-x-2.pr-6
  133. (for [ext exts]
  134. [:small.ext-label.is-del
  135. {:key ext :on-click #(del-ext dir ext)}
  136. [:span ext]
  137. (ui/icon "circle-minus")])
  138. (if (= dir @*ext-editing-dir)
  139. (input-auto-complete
  140. {:items (-get-all-formats)
  141. :close-modal? false
  142. :item-cp (fn [{:keys [value]}]
  143. [:div.ext-select-item value])
  144. :on-chosen (fn [{:keys [value]}]
  145. (add-ext dir value)
  146. (reset! *ext-editing-dir nil))
  147. :on-keydown (fn [^js e *input-val]
  148. (let [^js input (.-target e)]
  149. (case (.-which e)
  150. 27 ;; esc
  151. (do (if-not (string/blank? (.-value input))
  152. (reset! *input-val "")
  153. (reset! *ext-editing-dir nil))
  154. (util/stop e))
  155. :dune)))
  156. :input-opts {:class "cp__assets-alias-ext-input"
  157. :placeholder "E.g. mp3"
  158. :on-blur
  159. #(reset! *ext-editing-dir nil)}})
  160. [:small.ext-label.is-plus
  161. {:on-click #(reset! *ext-editing-dir dir)}
  162. (ui/icon "plus") "Acceptable file extensions"])]
  163. [:span.ctrls.flex.space-x-3.text-xs.opacity-30.hover:opacity-100.whitespace-nowrap.hidden.mt-1
  164. [:a {:on-click #(rm-dir dir)} (ui/icon "trash-x")]]]
  165. ])]
  166. [:p.pt-2
  167. (ui/button
  168. "+ Add directory"
  169. :on-click #(p/let [path (ipc/ipc :openDialog)]
  170. (when-not (or (string/blank? path)
  171. (pick-exist path))
  172. (confirm-dir path set-dir!)))
  173. :small? true)]]))
  174. (rum/defcs settings-content
  175. < rum/reactive
  176. (rum/local (state/sub :assets/alias-enabled?) ::alias-enabled?)
  177. [_state]
  178. (let [*pre-alias-enabled? (::alias-enabled? _state)
  179. alias-enabled? (state/sub :assets/alias-enabled?)
  180. alias-enabled-changed? (not= @*pre-alias-enabled? alias-enabled?)]
  181. [:div.cp__assets-settings.panel-wrap
  182. [:div.it
  183. [:label.block.text-sm.font-medium.leading-5.opacity-70
  184. "Alias directories"]
  185. [:div (ui/toggle
  186. alias-enabled?
  187. #(state/set-assets-alias-enabled! (not alias-enabled?))
  188. true)]
  189. [:span
  190. (when alias-enabled-changed? (restart-button))]]
  191. (when alias-enabled?
  192. [:div.pt-4
  193. [:h2.font-bold.opacity-80 "Selected directories:"]
  194. (alias-directories)])]))