mirror of
https://github.com/penpot/penpot.git
synced 2026-05-26 02:13:46 +00:00
Mainly removes an inconsistent use of path params and normalize all routes to use query params for make it extensible without breaking urls.
630 lines
24 KiB
Clojure
630 lines
24 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.grid
|
|
(:require-macros [app.main.style :as stl])
|
|
(:require
|
|
[app.common.data :as d]
|
|
[app.common.data.macros :as dm]
|
|
[app.common.geom.point :as gpt]
|
|
[app.common.logging :as log]
|
|
[app.config :as cf]
|
|
[app.main.data.common :as dcm]
|
|
[app.main.data.dashboard :as dd]
|
|
[app.main.data.notifications :as ntf]
|
|
[app.main.data.project :as dpj]
|
|
[app.main.data.team :as dtm]
|
|
[app.main.features :as features]
|
|
[app.main.fonts :as fonts]
|
|
[app.main.rasterizer :as thr]
|
|
[app.main.refs :as refs]
|
|
[app.main.render :as render]
|
|
[app.main.repo :as rp]
|
|
[app.main.store :as st]
|
|
[app.main.ui.components.color-bullet :as bc]
|
|
[app.main.ui.dashboard.file-menu :refer [file-menu*]]
|
|
[app.main.ui.dashboard.import :refer [use-import-file]]
|
|
[app.main.ui.dashboard.inline-edition :refer [inline-edition]]
|
|
[app.main.ui.dashboard.placeholder :refer [empty-placeholder loading-placeholder]]
|
|
[app.main.ui.ds.product.loader :refer [loader*]]
|
|
[app.main.ui.hooks :as h]
|
|
[app.main.ui.icons :as i]
|
|
[app.main.worker :as wrk]
|
|
[app.util.color :as uc]
|
|
[app.util.dom :as dom]
|
|
[app.util.dom.dnd :as dnd]
|
|
[app.util.i18n :as i18n :refer [tr]]
|
|
[app.util.keyboard :as kbd]
|
|
[app.util.time :as dt]
|
|
[app.util.timers :as ts]
|
|
[beicon.v2.core :as rx]
|
|
[cuerdas.core :as str]
|
|
[rumext.v2 :as mf]))
|
|
|
|
(log/set-level! :debug)
|
|
|
|
;; --- Grid Item Thumbnail
|
|
|
|
(defn- persist-thumbnail
|
|
[file-id revn blob]
|
|
(let [params {:file-id file-id :revn revn :media blob}]
|
|
(->> (rp/cmd! :create-file-thumbnail params)
|
|
(rx/map :id))))
|
|
|
|
(defn render-thumbnail
|
|
[file-id revn]
|
|
(->> (wrk/ask! {:cmd :thumbnails/generate-for-file
|
|
:revn revn
|
|
:file-id file-id
|
|
:features (features/get-team-enabled-features @st/state)})
|
|
(rx/mapcat (fn [{:keys [fonts] :as result}]
|
|
(->> (fonts/render-font-styles fonts)
|
|
(rx/map (fn [styles]
|
|
(assoc result
|
|
:styles styles
|
|
:width 252))))))))
|
|
|
|
(defn- ask-for-thumbnail
|
|
"Creates some hooks to handle the files thumbnails cache"
|
|
[file-id revn]
|
|
(->> (render-thumbnail file-id revn)
|
|
(rx/mapcat thr/render)
|
|
(rx/mapcat (partial persist-thumbnail file-id revn))))
|
|
|
|
(mf/defc grid-item-thumbnail*
|
|
{::mf/props :obj
|
|
::mf/private true}
|
|
[{:keys [can-edit file]}]
|
|
(let [file-id (get file :id)
|
|
revn (get file :revn)
|
|
thumbnail-id (get file :thumbnail-id)
|
|
|
|
bg-color (dm/get-in file [:data :background])
|
|
|
|
container (mf/use-ref)
|
|
visible? (h/use-visible container :once? true)]
|
|
|
|
(mf/with-effect [file-id revn visible? thumbnail-id]
|
|
(when (and visible? (not thumbnail-id))
|
|
(->> (ask-for-thumbnail file-id revn)
|
|
(rx/subs! (fn [thumbnail-id]
|
|
(st/emit! (dd/set-file-thumbnail file-id thumbnail-id)))
|
|
(fn [cause]
|
|
(log/error :hint "unable to render thumbnail"
|
|
:file-if file-id
|
|
:revn revn
|
|
:message (ex-message cause)))))))
|
|
|
|
[:div {:class (stl/css :grid-item-th)
|
|
:style {:background-color bg-color}
|
|
:ref container}
|
|
(when visible?
|
|
(if thumbnail-id
|
|
[:img {:class (stl/css :grid-item-thumbnail-image)
|
|
:draggable (dm/str can-edit)
|
|
:src (cf/resolve-media thumbnail-id)
|
|
:loading "lazy"
|
|
:decoding "async"}]
|
|
[:> loader* {:class (stl/css :grid-loader)
|
|
:draggable (dm/str can-edit)
|
|
:overlay true
|
|
:title (tr "labels.loading")}]))]))
|
|
|
|
;; --- Grid Item Library
|
|
|
|
(def ^:private menu-icon
|
|
(i/icon-xref :menu (stl/css :menu-icon)))
|
|
|
|
(mf/defc grid-item-library*
|
|
{::mf/props :obj}
|
|
[{:keys [file]}]
|
|
(mf/with-effect [file]
|
|
(when file
|
|
(let [font-ids (map :font-id (get-in file [:library-summary :typographies :sample] []))]
|
|
(run! fonts/ensure-loaded! font-ids))))
|
|
|
|
[:div {:class (stl/css :grid-item-th :library)}
|
|
(if (nil? file)
|
|
[:> loader* {:class (stl/css :grid-loader)
|
|
:overlay true
|
|
:title (tr "labels.loading")}]
|
|
(let [summary (:library-summary file)
|
|
components (:components summary)
|
|
colors (:colors summary)
|
|
typographies (:typographies summary)]
|
|
[:*
|
|
(when (and (zero? (:count components)) (zero? (:count colors)) (zero? (:count typographies)))
|
|
[:*
|
|
[:div {:class (stl/css :asset-section)}
|
|
[:div {:class (stl/css :asset-title)}
|
|
[:span (tr "workspace.assets.components")]
|
|
[:span {:class (stl/css :num-assets)} (str "\u00A0(") 0 ")"]]] ;; Unicode 00A0 is non-breaking space
|
|
[:div {:class (stl/css :asset-section)}
|
|
[:div {:class (stl/css :asset-title)}
|
|
[:span (tr "workspace.assets.colors")]
|
|
[:span {:class (stl/css :num-assets)} (str "\u00A0(") 0 ")"]]] ;; Unicode 00A0 is non-breaking space
|
|
[:div {:class (stl/css :asset-section)}
|
|
[:div {:class (stl/css :asset-title)}
|
|
[:span (tr "workspace.assets.typography")]
|
|
[:span {:class (stl/css :num-assets)} (str "\u00A0(") 0 ")"]]]]) ;; Unicode 00A0 is non-breaking space
|
|
|
|
|
|
(when (pos? (:count components))
|
|
[:div {:class (stl/css :asset-section)}
|
|
[:div {:class (stl/css :asset-title)}
|
|
[:span (tr "workspace.assets.components")]
|
|
[:span {:class (stl/css :num-assets)} (str "\u00A0(") (:count components) ")"]] ;; Unicode 00A0 is non-breaking space
|
|
[:div {:class (stl/css :asset-list)}
|
|
(for [component (:sample components)]
|
|
(let [root-id (or (:main-instance-id component) (:id component))] ;; Check for components-v2 in library
|
|
[:div {:class (stl/css :asset-list-item)
|
|
:key (str "assets-component-" (:id component))}
|
|
[:& render/component-svg {:root-shape (get-in component [:objects root-id])
|
|
:objects (:objects component)}] ;; Components in the summary come loaded with objects, even in v2
|
|
[:div {:class (stl/css :name-block)}
|
|
[:span {:class (stl/css :item-name)
|
|
:title (:name component)}
|
|
(:name component)]]]))
|
|
(when (> (:count components) (count (:sample components)))
|
|
[:div {:class (stl/css :asset-list-item)}
|
|
[:div {:class (stl/css :name-block)}
|
|
[:span {:class (stl/css :item-name)} "(...)"]]])]])
|
|
|
|
(when (pos? (:count colors))
|
|
[:div {:class (stl/css :asset-section)}
|
|
[:div {:class (stl/css :asset-title)}
|
|
[:span (tr "workspace.assets.colors")]
|
|
[:span {:class (stl/css :num-assets)} (str "\u00A0(") (:count colors) ")"]] ;; Unicode 00A0 is non-breaking space
|
|
[:div {:class (stl/css :asset-list)}
|
|
(for [color (:sample colors)]
|
|
(let [default-name (cond
|
|
(:gradient color) (uc/gradient-type->string (get-in color [:gradient :type]))
|
|
(:color color) (:color color)
|
|
:else (:value color))]
|
|
[:div {:class (stl/css :asset-list-item :color-item)
|
|
:key (str "assets-color-" (:id color))}
|
|
[:& bc/color-bullet {:color {:color (:color color)
|
|
:id (:id color)
|
|
:opacity (:opacity color)}
|
|
:mini? true}]
|
|
[:div {:class (stl/css :name-block)}
|
|
[:span {:class (stl/css :color-name)} (:name color)]
|
|
(when-not (= (:name color) default-name)
|
|
[:span {:class (stl/css :color-value)} (:color color)])]]))
|
|
|
|
(when (> (:count colors) (count (:sample colors)))
|
|
[:div {:class (stl/css :asset-list-item)}
|
|
[:div {:class (stl/css :name-block)}
|
|
[:span {:class (stl/css :item-name)} "(...)"]]])]])
|
|
|
|
(when (pos? (:count typographies))
|
|
[:div {:class (stl/css :asset-section)}
|
|
[:div {:class (stl/css :asset-title)}
|
|
[:span (tr "workspace.assets.typography")]
|
|
[:span {:class (stl/css :num-assets)} (str "\u00A0(") (:count typographies) ")"]] ;; Unicode 00A0 is non-breaking space
|
|
[:div {:class (stl/css :asset-list)}
|
|
(for [typography (:sample typographies)]
|
|
[:div {:class (stl/css :asset-list-item)
|
|
:key (str "assets-typography-" (:id typography))}
|
|
[:div {:class (stl/css :typography-sample)
|
|
:style {:font-family (:font-family typography)
|
|
:font-weight (:font-weight typography)
|
|
:font-style (:font-style typography)}}
|
|
(tr "workspace.assets.typography.sample")]
|
|
[:div {:class (stl/css :name-block)}
|
|
[:span {:class (stl/css :item-name)
|
|
:title (:name typography)}
|
|
(:name typography)]]])
|
|
|
|
(when (> (:count typographies) (count (:sample typographies)))
|
|
[:div {:class (stl/css :asset-list-item)}
|
|
[:div {:class (stl/css :name-block)}
|
|
[:span {:class (stl/css :item-name)} "(...)"]]])]])]))])
|
|
|
|
;; --- Grid Item
|
|
|
|
(mf/defc grid-item-metadata
|
|
[{:keys [modified-at]}]
|
|
|
|
(let [locale (mf/deref i18n/locale)
|
|
time (dt/timeago modified-at {:locale locale})]
|
|
[:span {:class (stl/css :date)} time]))
|
|
|
|
(defn create-counter-element
|
|
[_element file-count]
|
|
(let [counter-el (dom/create-element "div")]
|
|
(dom/set-property! counter-el "class" (stl/css :drag-counter))
|
|
(dom/set-text! counter-el (str file-count))
|
|
counter-el))
|
|
|
|
(mf/defc grid-item*
|
|
{::mf/props :obj}
|
|
[{:keys [file origin can-edit selected-files]}]
|
|
(let [file-id (:id file)
|
|
|
|
is-library-view (= origin :libraries)
|
|
|
|
dashboard-local (mf/deref refs/dashboard-local)
|
|
file-menu-open? (:menu-open dashboard-local)
|
|
|
|
selected? (contains? selected-files file-id)
|
|
|
|
node-ref (mf/use-ref)
|
|
menu-ref (mf/use-ref)
|
|
|
|
on-menu-close
|
|
(mf/use-fn
|
|
(fn [_]
|
|
(st/emit! (dd/hide-file-menu))))
|
|
|
|
on-select
|
|
(mf/use-fn
|
|
(fn [event]
|
|
(when (or (not selected?) (> (count selected-files) 1))
|
|
(dom/stop-propagation event)
|
|
(let [shift? (kbd/shift? event)]
|
|
(when-not shift?
|
|
(st/emit! (dd/clear-selected-files)))
|
|
(st/emit! (dd/toggle-file-select file))))))
|
|
|
|
on-navigate
|
|
(mf/use-fn
|
|
(mf/deps file-id)
|
|
(fn [event]
|
|
(let [menu-icon (mf/ref-val menu-ref)
|
|
target (dom/get-target event)]
|
|
(when-not (dom/child? target menu-icon)
|
|
(st/emit! (dcm/go-to-workspace :file-id file-id))))))
|
|
|
|
on-drag-start
|
|
(mf/use-fn
|
|
(mf/deps selected-files can-edit)
|
|
(fn [event]
|
|
(st/emit! (dd/hide-file-menu))
|
|
(when can-edit
|
|
(let [offset (dom/get-offset-position (dom/event->native-event event))
|
|
|
|
select-current? (not (contains? selected-files (:id file)))
|
|
|
|
item-el (mf/ref-val node-ref)
|
|
counter-el (create-counter-element
|
|
item-el
|
|
(if select-current?
|
|
1
|
|
(count selected-files)))]
|
|
(when select-current?
|
|
(st/emit! (dd/clear-selected-files))
|
|
(st/emit! (dd/toggle-file-select file)))
|
|
|
|
(dnd/set-data! event "penpot/files" "dummy")
|
|
(dnd/set-allowed-effect! event "move")
|
|
|
|
;; set-drag-image requires that the element is rendered and
|
|
;; visible to the user at the moment of creating the ghost
|
|
;; image (to make a snapshot), but you may remove it right
|
|
;; afterwards, in the next render cycle.
|
|
(dom/append-child! item-el counter-el)
|
|
(dnd/set-drag-image! event item-el (:x offset) (:y offset))
|
|
(ts/raf #(.removeChild ^js item-el counter-el))))))
|
|
|
|
on-menu-click
|
|
(mf/use-fn
|
|
(mf/deps file selected?)
|
|
(fn [event]
|
|
(dom/stop-propagation event)
|
|
(dom/prevent-default event)
|
|
(when-not selected?
|
|
(when-not (kbd/shift? event)
|
|
(st/emit! (dd/clear-selected-files)))
|
|
(st/emit! (dd/toggle-file-select file)))
|
|
|
|
(let [client-position (dom/get-client-position event)
|
|
position (if (and (nil? (:y client-position)) (nil? (:x client-position)))
|
|
(let [target-element (dom/get-target event)
|
|
points (dom/get-bounding-rect target-element)
|
|
y (:top points)
|
|
x (:left points)]
|
|
(gpt/point x y))
|
|
client-position)]
|
|
(st/emit! (dd/show-file-menu-with-position file-id position)))))
|
|
|
|
edit
|
|
(mf/use-fn
|
|
(mf/deps file)
|
|
(fn [name]
|
|
(let [name (str/trim name)]
|
|
(when (not= name "")
|
|
(st/emit! (dd/rename-file (assoc file :name name)))))
|
|
(st/emit! (dd/stop-edit-file-name))))
|
|
|
|
on-edit
|
|
(mf/use-fn
|
|
(mf/deps file)
|
|
(fn [event]
|
|
(dom/stop-propagation event)
|
|
(st/emit! (dd/start-edit-file-name file-id))))
|
|
|
|
handle-key-down
|
|
(mf/use-callback
|
|
(mf/deps on-navigate on-select)
|
|
(fn [event]
|
|
(dom/stop-propagation event)
|
|
(when (kbd/enter? event)
|
|
(on-navigate event))
|
|
(when (kbd/shift? event)
|
|
(when (or (kbd/down-arrow? event) (kbd/left-arrow? event) (kbd/up-arrow? event) (kbd/right-arrow? event))
|
|
(on-select event)) ;; TODO Fix this
|
|
)))]
|
|
|
|
[:li {:class (stl/css-case :grid-item true
|
|
:project-th true
|
|
:library is-library-view)}
|
|
[:div
|
|
{:class (stl/css-case :selected selected?
|
|
:library is-library-view)
|
|
:ref node-ref
|
|
:role "button"
|
|
:title (:name file)
|
|
:draggable (dm/str can-edit)
|
|
:on-click on-select
|
|
:on-key-down handle-key-down
|
|
:on-double-click on-navigate
|
|
:on-drag-start on-drag-start
|
|
:on-context-menu on-menu-click}
|
|
|
|
[:div {:class (stl/css :overlay)}]
|
|
|
|
(if ^boolean is-library-view
|
|
[:> grid-item-library* {:file file}]
|
|
[:> grid-item-thumbnail* {:file file :can-edit can-edit}])
|
|
|
|
(when (and (:is-shared file) (not is-library-view))
|
|
[:div {:class (stl/css :item-badge)} i/library])
|
|
|
|
[:div {:class (stl/css :info-wrapper)}
|
|
[:div {:class (stl/css :item-info)}
|
|
(if (and (= file-id (:file-id dashboard-local)) (:edition dashboard-local))
|
|
[:& inline-edition {:content (:name file)
|
|
:on-end edit}]
|
|
[:h3 (:name file)])
|
|
[:& grid-item-metadata {:modified-at (:modified-at file)}]]
|
|
|
|
[:div {:class (stl/css-case :project-th-actions true :force-display (:menu-open dashboard-local))}
|
|
[:div
|
|
{:class (stl/css :project-th-icon :menu)
|
|
:tab-index "0"
|
|
:ref menu-ref
|
|
:id (str file-id "-action-menu")
|
|
:on-click on-menu-click
|
|
:on-key-down (fn [event]
|
|
(when (kbd/enter? event)
|
|
(dom/stop-propagation event)
|
|
(on-menu-click event)))}
|
|
menu-icon
|
|
(when (and selected? file-menu-open?)
|
|
;; When the menu is open we disable events in the dashboard. We need to force pointer events
|
|
;; so the menu can be handled
|
|
[:div {:style {:pointer-events "all"}}
|
|
[:> file-menu* {:files (vals selected-files)
|
|
:show (:menu-open dashboard-local)
|
|
:left (+ 24 (:x (:menu-pos dashboard-local)))
|
|
:top (:y (:menu-pos dashboard-local))
|
|
:can-edit can-edit
|
|
:navigate true
|
|
:on-edit on-edit
|
|
:on-menu-close on-menu-close
|
|
:origin origin
|
|
:parent-id (dm/str file-id "-action-menu")}]])]]]]]))
|
|
|
|
(mf/defc grid
|
|
{::mf/props :obj}
|
|
[{:keys [files project origin limit create-fn can-edit selected-files]}]
|
|
(let [dragging? (mf/use-state false)
|
|
project-id (:id project)
|
|
node-ref (mf/use-var nil)
|
|
|
|
on-finish-import
|
|
(mf/use-fn
|
|
(fn []
|
|
(st/emit! (dpj/fetch-files project-id)
|
|
(dtm/fetch-shared-files)
|
|
(dd/clear-selected-files))))
|
|
|
|
|
|
|
|
import-files (use-import-file project-id on-finish-import)
|
|
|
|
on-drag-enter
|
|
(mf/use-fn
|
|
(fn [e]
|
|
(when can-edit
|
|
(when (and (not (dnd/has-type? e "penpot/files"))
|
|
(or (dnd/has-type? e "Files")
|
|
(dnd/has-type? e "application/x-moz-file")))
|
|
(dom/prevent-default e)
|
|
(reset! dragging? true)))))
|
|
|
|
on-drag-over
|
|
(mf/use-fn
|
|
(fn [e]
|
|
(when (or (dnd/has-type? e "Files")
|
|
(dnd/has-type? e "application/x-moz-file"))
|
|
(dom/prevent-default e))))
|
|
|
|
on-drag-leave
|
|
(mf/use-fn
|
|
(fn [e]
|
|
(when-not (dnd/from-child? e)
|
|
(reset! dragging? false))))
|
|
|
|
on-drop
|
|
(mf/use-fn
|
|
(fn [e]
|
|
(if can-edit
|
|
(when (and (not (dnd/has-type? e "penpot/files"))
|
|
(or (dnd/has-type? e "Files")
|
|
(dnd/has-type? e "application/x-moz-file")))
|
|
(dom/prevent-default e)
|
|
(reset! dragging? false)
|
|
(import-files (.-files (.-dataTransfer e))))
|
|
(dom/prevent-default e))))]
|
|
|
|
[:div {:class (stl/css :dashboard-grid)
|
|
:dragabble (dm/str can-edit)
|
|
:on-drag-enter on-drag-enter
|
|
:on-drag-over on-drag-over
|
|
:on-drag-leave on-drag-leave
|
|
:on-drop on-drop
|
|
:ref node-ref}
|
|
(cond
|
|
(nil? files)
|
|
[:& loading-placeholder]
|
|
|
|
(seq files)
|
|
(for [[index slice] (d/enumerate (partition-all limit files))]
|
|
|
|
[:ul {:class (stl/css :grid-row) :key (dm/str index)}
|
|
(when @dragging?
|
|
[:li {:class (stl/css :grid-item)}])
|
|
(for [item slice]
|
|
[:> grid-item*
|
|
{:file item
|
|
:key (dm/str (:id item))
|
|
:origin origin
|
|
:selected-files selected-files
|
|
:can-edit can-edit}])])
|
|
|
|
:else
|
|
[:& empty-placeholder
|
|
{:limit limit
|
|
:can-edit can-edit
|
|
:create-fn create-fn
|
|
:origin origin}])]))
|
|
|
|
(mf/defc line-grid-row
|
|
[{:keys [files selected-files dragging? limit can-edit] :as props}]
|
|
(let [elements limit
|
|
limit (if dragging? (dec limit) limit)]
|
|
[:ul {:class (stl/css :grid-row :no-wrap)
|
|
:style {:grid-template-columns (dm/str "repeat(" elements ", 1fr)")}}
|
|
|
|
(when dragging?
|
|
[:li {:class (stl/css :grid-item :dragged)}])
|
|
|
|
(for [item (take limit files)]
|
|
[:> grid-item*
|
|
{:id (:id item)
|
|
:file item
|
|
:selected-files selected-files
|
|
:can-edit can-edit
|
|
:key (dm/str (:id item))}])]))
|
|
|
|
(mf/defc line-grid
|
|
[{:keys [project team files limit create-fn can-edit] :as props}]
|
|
(let [dragging? (mf/use-state false)
|
|
project-id (:id project)
|
|
team-id (:id team)
|
|
|
|
selected-files (mf/deref refs/selected-files)
|
|
selected-project (mf/deref refs/selected-project)
|
|
|
|
on-finish-import
|
|
(mf/use-fn
|
|
(fn []
|
|
(st/emit! (dd/fetch-recent-files)
|
|
(dd/clear-selected-files))))
|
|
|
|
import-files (use-import-file project-id on-finish-import)
|
|
|
|
on-drag-enter
|
|
(mf/use-fn
|
|
(mf/deps selected-project can-edit)
|
|
(fn [e]
|
|
(when can-edit
|
|
(cond
|
|
(dnd/has-type? e "penpot/files")
|
|
(do
|
|
(dom/prevent-default e)
|
|
(when-not (or (dnd/from-child? e)
|
|
(dnd/broken-event? e))
|
|
(when (not= selected-project project-id)
|
|
(reset! dragging? true))))
|
|
|
|
(or (dnd/has-type? e "Files")
|
|
(dnd/has-type? e "application/x-moz-file"))
|
|
(do
|
|
(dom/prevent-default e)
|
|
(reset! dragging? true))))))
|
|
|
|
on-drag-over
|
|
(mf/use-fn
|
|
(fn [e]
|
|
(when (or (dnd/has-type? e "penpot/files")
|
|
(dnd/has-type? e "Files")
|
|
(dnd/has-type? e "application/x-moz-file"))
|
|
(dom/prevent-default e))))
|
|
|
|
on-drag-leave
|
|
(mf/use-fn
|
|
(fn [e]
|
|
(when-not (dnd/from-child? e)
|
|
(reset! dragging? false))))
|
|
|
|
on-drop-success
|
|
(mf/use-fn
|
|
(fn []
|
|
(st/emit! (ntf/success (tr "dashboard.success-move-file"))
|
|
(dd/fetch-recent-files)
|
|
(dd/clear-selected-files))))
|
|
|
|
on-drop
|
|
(mf/use-fn
|
|
(mf/deps files selected-files can-edit)
|
|
(fn [e]
|
|
(if can-edit
|
|
(cond
|
|
(dnd/has-type? e "penpot/files")
|
|
(do
|
|
(reset! dragging? false)
|
|
(when (not= selected-project project-id)
|
|
(let [data {:ids (into #{} (keys selected-files))
|
|
:project-id project-id}
|
|
mdata {:on-success on-drop-success}]
|
|
(st/emit! (dd/move-files (with-meta data mdata))))))
|
|
|
|
(or (dnd/has-type? e "Files")
|
|
(dnd/has-type? e "application/x-moz-file"))
|
|
(do
|
|
(dom/prevent-default e)
|
|
(reset! dragging? false)
|
|
(import-files (.-files (.-dataTransfer e)))))
|
|
(dom/prevent-default e))))]
|
|
|
|
[:div {:class (stl/css :dashboard-grid)
|
|
:dragabble (dm/str can-edit)
|
|
:on-drag-enter on-drag-enter
|
|
:on-drag-over on-drag-over
|
|
:on-drag-leave on-drag-leave
|
|
:on-drop on-drop}
|
|
(cond
|
|
(nil? files)
|
|
[:& loading-placeholder]
|
|
|
|
(seq files)
|
|
[:& line-grid-row {:files files
|
|
:team-id team-id
|
|
:selected-files selected-files
|
|
:dragging? @dragging?
|
|
:can-edit can-edit
|
|
:limit limit}]
|
|
|
|
:else
|
|
[:& empty-placeholder
|
|
{:dragging? @dragging?
|
|
:limit limit
|
|
:can-edit can-edit
|
|
:create-fn create-fn}])]))
|