mirror of
https://github.com/penpot/penpot.git
synced 2026-05-23 08:53:39 +00:00
251 lines
8.3 KiB
Clojure
251 lines
8.3 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/.
|
|
;;
|
|
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
|
;; defined by the Mozilla Public License, v. 2.0.
|
|
;;
|
|
;; Copyright (c) 2020 UXBOX Labs SL
|
|
|
|
(ns app.main.ui.dashboard.grid
|
|
(:require
|
|
[app.common.uuid :as uuid]
|
|
[app.common.math :as mth]
|
|
[app.config :as cfg]
|
|
[app.main.data.dashboard :as dd]
|
|
[app.main.fonts :as fonts]
|
|
[app.main.store :as st]
|
|
[app.main.ui.components.context-menu :refer [context-menu]]
|
|
[app.main.ui.icons :as i]
|
|
[app.main.ui.keyboard :as kbd]
|
|
[app.main.ui.modal :as modal]
|
|
[app.main.worker :as wrk]
|
|
[app.util.dom :as dom]
|
|
[app.util.i18n :as i18n :refer [t tr]]
|
|
[app.util.router :as rt]
|
|
[app.util.time :as dt]
|
|
[app.util.timers :as ts]
|
|
[beicon.core :as rx]
|
|
[cuerdas.core :as str]
|
|
[lambdaisland.uri :as uri]
|
|
[rumext.alpha :as mf]))
|
|
|
|
;; --- Grid Item Thumbnail
|
|
|
|
(mf/defc grid-item-thumbnail
|
|
{::mf/wrap [mf/memo]}
|
|
[{:keys [file] :as props}]
|
|
(let [container (mf/use-ref)]
|
|
(mf/use-effect
|
|
(mf/deps (:id file))
|
|
(fn []
|
|
(->> (wrk/ask! {:cmd :thumbnails/generate
|
|
:file-id (:id file)
|
|
:page-id (get-in file [:data :pages 0])})
|
|
(rx/subs (fn [{:keys [svg fonts]}]
|
|
(run! fonts/ensure-loaded! fonts)
|
|
(when-let [node (mf/ref-val container)]
|
|
(set! (.-innerHTML ^js node) svg)))))))
|
|
[:div.grid-item-th {:style {:background-color (get-in file [:data :options :background])}
|
|
:ref container}]))
|
|
|
|
;; --- Grid Item
|
|
|
|
(mf/defc grid-item-metadata
|
|
[{:keys [modified-at]}]
|
|
(let [locale (mf/deref i18n/locale)
|
|
time (dt/timeago modified-at {:locale locale})]
|
|
(str (t locale "ds.updated-at" time))))
|
|
|
|
(mf/defc grid-item
|
|
{:wrap [mf/memo]}
|
|
[{:keys [id file] :as props}]
|
|
(let [local (mf/use-state {:menu-open false :edition false})
|
|
locale (mf/deref i18n/locale)
|
|
|
|
delete (mf/use-callback (mf/deps id) #(st/emit! (dd/delete-file file)))
|
|
add-shared (mf/use-callback (mf/deps id) #(st/emit! (dd/set-file-shared id true)))
|
|
del-shared (mf/use-callback (mf/deps id) #(st/emit! (dd/set-file-shared id false)))
|
|
on-close (mf/use-callback #(swap! local assoc :menu-open false))
|
|
|
|
on-delete
|
|
(mf/use-callback
|
|
(mf/deps id)
|
|
(fn [event]
|
|
(dom/stop-propagation event)
|
|
(modal/show! :confirm-dialog {:on-accept delete})))
|
|
|
|
on-navigate
|
|
(mf/use-callback
|
|
(mf/deps id)
|
|
(fn []
|
|
(let [pparams {:project-id (:project-id file)
|
|
:file-id (:id file)}
|
|
qparams {:page-id (first (get-in file [:data :pages]))}]
|
|
(st/emit! (rt/nav :workspace pparams qparams)))))
|
|
|
|
on-add-shared
|
|
(mf/use-callback
|
|
(mf/deps id)
|
|
(fn [event]
|
|
(dom/stop-propagation event)
|
|
(modal/show! :confirm-dialog
|
|
{:message (t locale "dashboard.grid.add-shared-message" (:name file))
|
|
:hint (t locale "dashboard.grid.add-shared-hint")
|
|
:accept-text (t locale "dashboard.grid.add-shared-accept")
|
|
:not-danger? true
|
|
:on-accept add-shared})))
|
|
|
|
on-edit
|
|
(mf/use-callback
|
|
(mf/deps id)
|
|
(fn [event]
|
|
(dom/stop-propagation event)
|
|
(swap! local assoc :edition true)))
|
|
|
|
on-del-shared
|
|
(mf/use-callback
|
|
(mf/deps id)
|
|
(fn [event]
|
|
(dom/stop-propagation event)
|
|
(modal/show! :confirm-dialog
|
|
{:message (t locale "dashboard.grid.remove-shared-message" (:name file))
|
|
:hint (t locale "dashboard.grid.remove-shared-hint")
|
|
:accept-text (t locale "dashboard.grid.remove-shared-accept")
|
|
:not-danger? false
|
|
:on-accept del-shared})))
|
|
|
|
on-menu-click
|
|
(mf/use-callback
|
|
(mf/deps id)
|
|
(fn [event]
|
|
(dom/stop-propagation event)
|
|
(swap! local assoc :menu-open true)))
|
|
|
|
on-blur
|
|
(mf/use-callback
|
|
(mf/deps id)
|
|
(fn [event]
|
|
(let [name (-> event dom/get-target dom/get-value)
|
|
file (assoc file :name name)]
|
|
(st/emit! (dd/rename-file file))
|
|
(swap! local assoc :edition false))))
|
|
|
|
on-key-down
|
|
(mf/use-callback
|
|
#(cond
|
|
(kbd/enter? %) (on-blur %)
|
|
(kbd/esc? %) (swap! local assoc :edition false)))
|
|
|
|
]
|
|
[:div.grid-item.project-th {:on-click on-navigate}
|
|
[:div.overlay]
|
|
[:& grid-item-thumbnail {:file file}]
|
|
(when (:is-shared file)
|
|
[:div.item-badge
|
|
i/library])
|
|
[:div.item-info
|
|
(if (:edition @local)
|
|
[:input.element-name {:type "text"
|
|
:auto-focus true
|
|
:on-key-down on-key-down
|
|
:on-blur on-blur
|
|
:default-value (:name file)}]
|
|
[:h3 (:name file)])
|
|
[:& grid-item-metadata {:modified-at (:modified-at file)}]]
|
|
[:div.project-th-actions {:class (dom/classnames
|
|
:force-display (:menu-open @local))}
|
|
[:div.project-th-icon.menu
|
|
{:on-click on-menu-click}
|
|
i/actions]
|
|
[:& context-menu {:on-close on-close
|
|
:show (:menu-open @local)
|
|
:options [[(t locale "dashboard.grid.rename") on-edit]
|
|
[(t locale "dashboard.grid.delete") on-delete]
|
|
(if (:is-shared file)
|
|
[(t locale "dashboard.grid.remove-shared") on-del-shared]
|
|
[(t locale "dashboard.grid.add-shared") on-add-shared])]}]]]))
|
|
|
|
(mf/defc empty-placeholder
|
|
[]
|
|
(let [locale (mf/deref i18n/locale)]
|
|
[:div.grid-empty-placeholder
|
|
[:div.icon i/file-html]
|
|
[:div.text (t locale "dashboard.grid.empty-files")]]))
|
|
|
|
(mf/defc grid
|
|
[{:keys [id opts files hide-new?] :as props}]
|
|
(let [locale (mf/deref i18n/locale)
|
|
click #(st/emit! (dd/create-file id))]
|
|
[:section.dashboard-grid
|
|
(if (pos? (count files))
|
|
[:div.dashboard-grid-row
|
|
(when (not hide-new?)
|
|
[:div.grid-item.add-file {:on-click click}
|
|
[:span (t locale "dashboard.new-file")]])
|
|
|
|
(for [item files]
|
|
[:& grid-item
|
|
{:id (:id item)
|
|
:file item
|
|
:key (:id item)}])]
|
|
|
|
[:& empty-placeholder])]))
|
|
|
|
(mf/defc line-grid-row
|
|
[{:keys [locale files] :as props}]
|
|
(let [rowref (mf/use-ref)
|
|
|
|
width (mf/use-state 900)
|
|
limit (mf/use-state 1)
|
|
itemsize 290]
|
|
|
|
(mf/use-layout-effect
|
|
(mf/deps width)
|
|
(fn []
|
|
(let [node (mf/ref-val rowref)
|
|
obs (new js/ResizeObserver
|
|
(fn [entries x]
|
|
(let [data (first entries)
|
|
rect (.-contentRect ^js data)]
|
|
(reset! width (.-width ^js rect)))))
|
|
|
|
nitems (/ @width itemsize)
|
|
num (mth/floor nitems)]
|
|
|
|
(.observe ^js obs node)
|
|
|
|
(cond
|
|
(< (* itemsize (count files)) @width)
|
|
(reset! limit num)
|
|
|
|
(< nitems (+ num 0.51))
|
|
(reset! limit (dec num))
|
|
|
|
:else
|
|
(reset! limit num))
|
|
(fn []
|
|
(.disconnect ^js obs)))))
|
|
|
|
[:div.grid-row.no-wrap {:ref rowref}
|
|
(for [item (take @limit files)]
|
|
[:& grid-item
|
|
{:id (:id item)
|
|
:file item
|
|
:key (:id item)}])
|
|
(when (> (count files) @limit)
|
|
[:div.grid-item.placeholder
|
|
[:div.placeholder-icon i/arrow-down]
|
|
[:div.placeholder-label "Show all files"]])]))
|
|
|
|
(mf/defc line-grid
|
|
[{:keys [project-id opts files] :as props}]
|
|
(let [locale (mf/deref i18n/locale)
|
|
click #(st/emit! (dd/create-file project-id))]
|
|
[:section.dashboard-grid
|
|
(if (pos? (count files))
|
|
[:& line-grid-row {:files files
|
|
:locale locale}]
|
|
[:& empty-placeholder])]))
|
|
|