mirror of
https://github.com/penpot/penpot.git
synced 2025-08-02 00:48:28 +02:00
277 lines
9.2 KiB
Clojure
277 lines
9.2 KiB
Clojure
;; This Source Code Form is subject to the terms of the Mozilla Public
|
|
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
;;
|
|
;; Copyright (c) KALEIDOS INC
|
|
|
|
(ns app.main.ui.dashboard.templates
|
|
(:require-macros [app.main.style :as stl])
|
|
(:require
|
|
[app.common.data.macros :as dm]
|
|
[app.config :as cf]
|
|
[app.main.data.dashboard :as dd]
|
|
[app.main.data.events :as ev]
|
|
[app.main.data.modal :as modal]
|
|
[app.main.refs :as refs]
|
|
[app.main.store :as st]
|
|
[app.main.ui.icons :as i]
|
|
[app.util.dom :as dom]
|
|
[app.util.i18n :refer [tr]]
|
|
[app.util.keyboard :as kbd]
|
|
[app.util.router :as rt]
|
|
[app.util.storage :as storage]
|
|
[okulary.core :as l]
|
|
[potok.v2.core :as ptk]
|
|
[rumext.v2 :as mf]))
|
|
|
|
(def ^:private arrow-icon
|
|
(i/icon-xref :arrow (stl/css :arrow-icon)))
|
|
|
|
(def ^:private download-icon
|
|
(i/icon-xref :download (stl/css :download-icon)))
|
|
|
|
(def builtin-templates
|
|
(l/derived :builtin-templates st/state))
|
|
|
|
(defn- import-template!
|
|
[template team-id project-id default-project-id section]
|
|
(letfn [(on-finish []
|
|
(st/emit!
|
|
(ptk/event ::ev/event {::ev/name "import-template-finish"
|
|
::ev/origin "dashboard"
|
|
:template (:name template)
|
|
:section section})
|
|
|
|
(when-not (some? project-id)
|
|
(rt/nav :dashboard-files
|
|
{:team-id team-id
|
|
:project-id default-project-id}))))]
|
|
|
|
(st/emit!
|
|
(ptk/event ::ev/event {::ev/name "import-template-launch"
|
|
::ev/origin "dashboard"
|
|
:template (:name template)
|
|
:section section})
|
|
|
|
(modal/show
|
|
{:type :import
|
|
:project-id (or project-id default-project-id)
|
|
:files []
|
|
:template template
|
|
:on-finish-import on-finish}))))
|
|
|
|
(mf/defc title*
|
|
{::mf/props :obj
|
|
::mf/private true}
|
|
[{:keys [on-click is-collapsed]}]
|
|
(let [on-key-down
|
|
(mf/use-fn
|
|
(mf/deps on-click)
|
|
(fn [event]
|
|
(when (kbd/enter? event)
|
|
(dom/stop-propagation event)
|
|
(dom/prevent-default event)
|
|
(on-click event))))]
|
|
|
|
[:div {:class (stl/css :title)}
|
|
[:button {:tab-index "0"
|
|
:class (stl/css :title-btn)
|
|
:on-click on-click
|
|
:on-key-down on-key-down}
|
|
[:span {:class (stl/css :title-text)}
|
|
(tr "dashboard.libraries-and-templates")]
|
|
(if ^boolean is-collapsed
|
|
[:span {:class (stl/css :title-icon :title-icon-collapsed)}
|
|
arrow-icon]
|
|
[:span {:class (stl/css :title-icon)}
|
|
arrow-icon])]]))
|
|
|
|
(mf/defc card-item
|
|
{::mf/wrap-props false}
|
|
[{:keys [item index is-visible collapsed on-import]}]
|
|
(let [id (dm/str "card-container-" index)
|
|
thb (assoc cf/public-uri :path (dm/str "/images/thumbnails/template-" (:id item) ".jpg"))
|
|
|
|
on-click
|
|
(mf/use-fn
|
|
(mf/deps on-import)
|
|
(fn [event]
|
|
(on-import item event)))
|
|
|
|
on-key-down
|
|
(mf/use-fn
|
|
(mf/deps on-import)
|
|
(fn [event]
|
|
(when (kbd/enter? event)
|
|
(dom/stop-propagation event)
|
|
(on-import item event))))]
|
|
|
|
[:a {:class (stl/css :card-container)
|
|
:tab-index (if (or (not is-visible) collapsed) "-1" "0")
|
|
:id id
|
|
:data-index index
|
|
:on-click on-click
|
|
:on-mouse-down dom/prevent-default
|
|
:on-key-down on-key-down}
|
|
[:div {:class (stl/css :template-card)}
|
|
[:div {:class (stl/css :img-container)}
|
|
[:img {:src (dm/str thb)
|
|
:alt (:name item)
|
|
:loading "lazy"
|
|
:decoding "async"}]]
|
|
[:div {:class (stl/css :card-name)}
|
|
[:span {:class (stl/css :card-text)} (:name item)]
|
|
download-icon]]]))
|
|
|
|
(mf/defc card-item-link
|
|
{::mf/wrap-props false}
|
|
[{:keys [total is-visible collapsed section]}]
|
|
(let [id (dm/str "card-container-" total)
|
|
|
|
on-click
|
|
(mf/use-fn
|
|
(mf/deps section)
|
|
(fn []
|
|
(st/emit! (ptk/event ::ev/event {::ev/name "explore-libraries-click"
|
|
::ev/origin "dashboard"
|
|
:section section}))))
|
|
|
|
on-key-down
|
|
(mf/use-fn
|
|
(mf/deps on-click)
|
|
(fn [event]
|
|
(when (kbd/enter? event)
|
|
(dom/stop-propagation event)
|
|
(on-click event))))]
|
|
|
|
[:div {:class (stl/css :card-container)}
|
|
[:div {:class (stl/css :template-card)}
|
|
[:div {:class (stl/css :img-container)}
|
|
[:a {:id id
|
|
:tab-index (if (or (not is-visible) collapsed) "-1" "0")
|
|
:href "https://penpot.app/libraries-templates"
|
|
:target "_blank"
|
|
:on-click on-click
|
|
:on-key-down on-key-down}
|
|
[:div {:class (stl/css :template-link)}
|
|
[:div {:class (stl/css :template-link-title)} (tr "dashboard.libraries-and-templates")]
|
|
[:div {:class (stl/css :template-link-text)} (tr "dashboard.libraries-and-templates.explore")]]]]]]))
|
|
|
|
(mf/defc templates-section
|
|
{::mf/wrap-props false}
|
|
[{:keys [default-project-id profile project-id team-id]}]
|
|
(let [templates (mf/deref builtin-templates)
|
|
templates (mf/with-memo [templates]
|
|
(filterv #(and
|
|
(not= (:id %) "welcome")
|
|
(not= (:id %) "tutorial-for-beginners")) templates))
|
|
|
|
route (mf/deref refs/route)
|
|
route-name (get-in route [:data :name])
|
|
section (if (= route-name :dashboard-files)
|
|
(if (= project-id default-project-id)
|
|
"dashboard-drafts"
|
|
"dashboard-project")
|
|
(name route-name))
|
|
|
|
collapsed* (mf/use-state
|
|
#(get storage/global ::collapsed))
|
|
collapsed (deref collapsed*)
|
|
|
|
|
|
|
|
can-move (mf/use-state {:left false :right true})
|
|
|
|
total (count templates)
|
|
|
|
;; We need space for total plus the libraries&templates link
|
|
content-ref (mf/use-ref)
|
|
|
|
move-left (fn [] (dom/scroll-by! (mf/ref-val content-ref) -300 0))
|
|
move-right (fn [] (dom/scroll-by! (mf/ref-val content-ref) 300 0))
|
|
|
|
on-toggle-collapse
|
|
(mf/use-fn
|
|
(fn [_event]
|
|
(swap! collapsed* not)))
|
|
|
|
on-scroll
|
|
(mf/use-fn
|
|
(fn [e]
|
|
(let [scroll (dom/get-target-scroll e)
|
|
scroll-left (:scroll-left scroll)
|
|
scroll-available (- (:scroll-width scroll) scroll-left)
|
|
client-rect (dom/get-client-size (dom/get-target e))
|
|
client-width (unchecked-get client-rect "width")]
|
|
|
|
(reset! can-move {:left (> scroll-left 0)
|
|
:right (> scroll-available client-width)}))))
|
|
|
|
on-move-left
|
|
(mf/use-fn #(move-left))
|
|
|
|
on-move-left-key-down
|
|
(mf/use-fn #(move-left))
|
|
|
|
on-move-right
|
|
(mf/use-fn #(move-right))
|
|
|
|
on-move-right-key-down
|
|
(mf/use-fn #(move-right))
|
|
|
|
on-import-template
|
|
(mf/use-fn
|
|
(mf/deps default-project-id project-id section templates team-id)
|
|
(fn [template _event]
|
|
(import-template! template team-id project-id default-project-id section)))]
|
|
|
|
(mf/with-effect [content-ref templates]
|
|
(let [content (mf/ref-val content-ref)]
|
|
(when (and (some? content) (some? templates))
|
|
(dom/scroll-to content #js {:behavior "instant" :left 0 :top 0})
|
|
(dom/dispatch-event content (dom/event "scroll")))))
|
|
|
|
(mf/with-effect [profile collapsed]
|
|
(swap! storage/global assoc ::collapsed collapsed)
|
|
|
|
(when (and profile (not collapsed))
|
|
(st/emit! (dd/fetch-builtin-templates))))
|
|
|
|
[:div {:class (stl/css-case :dashboard-templates-section true
|
|
:collapsed collapsed)}
|
|
[:> title* {:on-click on-toggle-collapse
|
|
:is-collapsed collapsed}]
|
|
|
|
[:div {:class (stl/css :content)
|
|
:on-scroll on-scroll
|
|
:ref content-ref}
|
|
|
|
(for [index (range (count templates))]
|
|
[:& card-item
|
|
{:on-import on-import-template
|
|
:item (nth templates index)
|
|
:index index
|
|
:key index
|
|
:is-visible true
|
|
:collapsed collapsed}])
|
|
|
|
[:& card-item-link
|
|
{:is-visible true
|
|
:collapsed collapsed
|
|
:section section
|
|
:total total}]]
|
|
|
|
(when (:left @can-move)
|
|
[:button {:class (stl/css :move-button :move-left)
|
|
:tab-index (if ^boolean collapsed "-1" "0")
|
|
:on-click on-move-left
|
|
:on-key-down on-move-left-key-down}
|
|
arrow-icon])
|
|
|
|
(when (:right @can-move)
|
|
[:button {:class (stl/css :move-button :move-right)
|
|
:tab-index (if collapsed "-1" "0")
|
|
:on-click on-move-right
|
|
:aria-label (tr "labels.next")
|
|
:on-key-down on-move-right-key-down}
|
|
arrow-icon])]))
|