mirror of
https://github.com/penpot/penpot.git
synced 2025-05-18 14:36:10 +02:00
🎉 Drag and drop files in the dashboard
This commit is contained in:
parent
81a604dca2
commit
c2332331ce
11 changed files with 332 additions and 81 deletions
|
@ -177,13 +177,17 @@
|
||||||
background-color: $color-white;
|
background-color: $color-white;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
border-color: $color-primary;
|
border: 2px solid $color-primary;
|
||||||
|
|
||||||
.project-th-actions {
|
.project-th-actions {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
border: 2px solid $color-primary;
|
||||||
|
}
|
||||||
|
|
||||||
.project-th-actions {
|
.project-th-actions {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
bottom: 2px;
|
bottom: 2px;
|
||||||
|
@ -234,7 +238,6 @@
|
||||||
.project-th-actions.force-display {
|
.project-th-actions.force-display {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IMAGES SECTION
|
// IMAGES SECTION
|
||||||
|
@ -275,6 +278,21 @@
|
||||||
margin-top: 15px;
|
margin-top: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.drag-counter {
|
||||||
|
position: absolute;
|
||||||
|
top: 5px;
|
||||||
|
left: 4px;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
background-color: $color-primary;
|
||||||
|
border-radius: 50%;
|
||||||
|
color: $color-black;
|
||||||
|
font-size: $fs18;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// STYLES FOR LIBRARIES
|
// STYLES FOR LIBRARIES
|
||||||
|
|
|
@ -249,6 +249,10 @@
|
||||||
background-color: $color-primary;
|
background-color: $color-primary;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.dragging {
|
||||||
|
background-color: $color-primary-lighter;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
flex: 1 0 0;
|
flex: 1 0 0;
|
||||||
margin-right: $medium;
|
margin-right: $medium;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
&.search {
|
&.search {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
|
|
|
@ -57,6 +57,10 @@
|
||||||
::modified-at
|
::modified-at
|
||||||
::project-id]))
|
::project-id]))
|
||||||
|
|
||||||
|
(s/def ::set-of-uuid
|
||||||
|
(s/every ::us/uuid :kind set?))
|
||||||
|
|
||||||
|
(declare clear-selected-files)
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;; Data Fetching
|
;; Data Fetching
|
||||||
|
@ -145,8 +149,10 @@
|
||||||
|
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state stream]
|
(watch [_ state stream]
|
||||||
(->> (rp/query :search-files params)
|
(rx/concat
|
||||||
(rx/map #(partial fetched %)))))))
|
(->> (rp/query :search-files params)
|
||||||
|
(rx/map #(partial fetched %)))
|
||||||
|
(rx/of (clear-selected-files)))))))
|
||||||
|
|
||||||
;; --- Fetch Files
|
;; --- Fetch Files
|
||||||
|
|
||||||
|
@ -158,8 +164,10 @@
|
||||||
(ptk/reify ::fetch-files
|
(ptk/reify ::fetch-files
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state stream]
|
(watch [_ state stream]
|
||||||
(->> (rp/query :files params)
|
(rx/concat
|
||||||
(rx/map #(partial fetched %)))))))
|
(->> (rp/query :files params)
|
||||||
|
(rx/map #(partial fetched %)))
|
||||||
|
(rx/of (clear-selected-files)))))))
|
||||||
|
|
||||||
;; --- Fetch Shared Files
|
;; --- Fetch Shared Files
|
||||||
|
|
||||||
|
@ -171,8 +179,10 @@
|
||||||
(ptk/reify ::fetch-shared-files
|
(ptk/reify ::fetch-shared-files
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state stream]
|
(watch [_ state stream]
|
||||||
(->> (rp/query :shared-files {:team-id team-id})
|
(rx/concat
|
||||||
(rx/map #(partial fetched %)))))))
|
(->> (rp/query :shared-files {:team-id team-id})
|
||||||
|
(rx/map #(partial fetched %)))
|
||||||
|
(rx/of (clear-selected-files)))))))
|
||||||
|
|
||||||
;; --- Fetch recent files
|
;; --- Fetch recent files
|
||||||
|
|
||||||
|
@ -185,8 +195,10 @@
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state stream]
|
(watch [_ state stream]
|
||||||
(let [params {:team-id team-id}]
|
(let [params {:team-id team-id}]
|
||||||
(->> (rp/query :recent-files params)
|
(rx/concat
|
||||||
(rx/map #(recent-files-fetched team-id %)))))))
|
(->> (rp/query :recent-files params)
|
||||||
|
(rx/map #(recent-files-fetched team-id %)))
|
||||||
|
(rx/of (clear-selected-files)))))))
|
||||||
|
|
||||||
(defn recent-files-fetched
|
(defn recent-files-fetched
|
||||||
[team-id files]
|
[team-id files]
|
||||||
|
@ -202,6 +214,41 @@
|
||||||
state
|
state
|
||||||
projects)))))
|
projects)))))
|
||||||
|
|
||||||
|
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;; Data Selection
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
||||||
|
(defn clear-selected-files
|
||||||
|
[]
|
||||||
|
(ptk/reify ::clear-file-select
|
||||||
|
ptk/UpdateEvent
|
||||||
|
(update [_ state]
|
||||||
|
(update state :dashboard-local
|
||||||
|
assoc :selected-files #{}
|
||||||
|
:selected-project nil))))
|
||||||
|
|
||||||
|
(defn toggle-file-select
|
||||||
|
[{:keys [file] :as params}]
|
||||||
|
(ptk/reify ::toggle-file-select
|
||||||
|
ptk/UpdateEvent
|
||||||
|
(update [_ state]
|
||||||
|
(let [file-id (:id file)
|
||||||
|
selected-project (get-in state [:dashboard-local
|
||||||
|
:selected-project])]
|
||||||
|
(if (or (nil? selected-project)
|
||||||
|
(= selected-project (:project-id file)))
|
||||||
|
(update state :dashboard-local
|
||||||
|
(fn [local]
|
||||||
|
(-> local
|
||||||
|
(update :selected-files
|
||||||
|
#(if (contains? % file-id)
|
||||||
|
(disj % file-id)
|
||||||
|
(conj % file-id)))
|
||||||
|
(assoc :selected-project
|
||||||
|
(:project-id file)))))
|
||||||
|
state)))))
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;; Data Modification
|
;; Data Modification
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
@ -556,18 +603,18 @@
|
||||||
|
|
||||||
;; --- Move File
|
;; --- Move File
|
||||||
|
|
||||||
(defn move-file
|
(defn move-files
|
||||||
[{:keys [id project-id] :as params}]
|
[{:keys [ids project-id] :as params}]
|
||||||
(us/assert ::us/uuid id)
|
(us/assert ::set-of-uuid ids)
|
||||||
(us/assert ::us/uuid project-id)
|
(us/assert ::us/uuid project-id)
|
||||||
(ptk/reify ::move-file
|
(ptk/reify ::move-files
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state stream]
|
(watch [_ state stream]
|
||||||
(let [{:keys [on-success on-error]
|
(let [{:keys [on-success on-error]
|
||||||
:or {on-success identity
|
:or {on-success identity
|
||||||
on-error identity}} (meta params)]
|
on-error identity}} (meta params)]
|
||||||
|
|
||||||
(->> (rp/mutation! :move-files {:ids #{id}
|
(->> (rp/mutation! :move-files {:ids ids
|
||||||
:project-id project-id})
|
:project-id project-id})
|
||||||
(rx/tap on-success)
|
(rx/tap on-success)
|
||||||
(rx/catch on-error))))))
|
(rx/catch on-error))))))
|
||||||
|
|
|
@ -40,6 +40,16 @@
|
||||||
(def dashboard-local
|
(def dashboard-local
|
||||||
(l/derived :dashboard-local st/state))
|
(l/derived :dashboard-local st/state))
|
||||||
|
|
||||||
|
(def dashboard-selected-files
|
||||||
|
(l/derived (fn [state]
|
||||||
|
(get-in state [:dashboard-local :selected-files] #{}))
|
||||||
|
st/state))
|
||||||
|
|
||||||
|
(def dashboard-selected-project
|
||||||
|
(l/derived (fn [state]
|
||||||
|
(get-in state [:dashboard-local :selected-project]))
|
||||||
|
st/state))
|
||||||
|
|
||||||
;; ---- Workspace refs
|
;; ---- Workspace refs
|
||||||
|
|
||||||
(def workspace-local
|
(def workspace-local
|
||||||
|
|
|
@ -61,7 +61,7 @@
|
||||||
|
|
||||||
(mf/defc dashboard-content
|
(mf/defc dashboard-content
|
||||||
[{:keys [team projects project section search-term profile] :as props}]
|
[{:keys [team projects project section search-term profile] :as props}]
|
||||||
[:div.dashboard-content
|
[:div.dashboard-content {:on-click (st/emitf (dd/clear-selected-files))}
|
||||||
(case section
|
(case section
|
||||||
:dashboard-projects
|
:dashboard-projects
|
||||||
[:& projects-section {:team team :projects projects}]
|
[:& projects-section {:team team :projects projects}]
|
||||||
|
|
|
@ -23,11 +23,12 @@
|
||||||
[rumext.alpha :as mf]))
|
[rumext.alpha :as mf]))
|
||||||
|
|
||||||
(mf/defc file-menu
|
(mf/defc file-menu
|
||||||
[{:keys [file show? on-edit on-menu-close top left] :as props}]
|
[{:keys [file show? on-edit on-menu-close top left navigate?] :as props}]
|
||||||
(assert (some? file) "missing `file` prop")
|
(assert (some? file) "missing `file` prop")
|
||||||
(assert (boolean? show?) "missing `show?` prop")
|
(assert (boolean? show?) "missing `show?` prop")
|
||||||
(assert (fn? on-edit) "missing `on-edit` prop")
|
(assert (fn? on-edit) "missing `on-edit` prop")
|
||||||
(assert (fn? on-menu-close) "missing `on-menu-close` prop")
|
(assert (fn? on-menu-close) "missing `on-menu-close` prop")
|
||||||
|
(assert (boolean? navigate?) "missing `navigate?` prop")
|
||||||
(let [top (or top 0)
|
(let [top (or top 0)
|
||||||
left (or left 0)
|
left (or left 0)
|
||||||
|
|
||||||
|
@ -81,16 +82,18 @@
|
||||||
(mf/use-callback
|
(mf/use-callback
|
||||||
(mf/deps file)
|
(mf/deps file)
|
||||||
(fn [team-id project-id]
|
(fn [team-id project-id]
|
||||||
(let [data {:id (:id file)
|
(let [data {:ids #{(:id file)}
|
||||||
:project-id project-id}
|
:project-id project-id}
|
||||||
|
|
||||||
mdata {:on-success
|
mdata {:on-success
|
||||||
(st/emitf (dm/success (tr "dashboard.success-move-file"))
|
(st/emitf (dm/success (tr "dashboard.success-move-file"))
|
||||||
(rt/nav :dashboard-files
|
(if navigate?
|
||||||
{:team-id team-id
|
(rt/nav :dashboard-files
|
||||||
:project-id project-id}))}]
|
{:team-id team-id
|
||||||
|
:project-id project-id})
|
||||||
|
(dd/fetch-recent-files {:team-id team-id})))}]
|
||||||
|
|
||||||
(st/emitf (dd/move-file (with-meta data mdata))))))
|
(st/emitf (dd/move-files (with-meta data mdata))))))
|
||||||
|
|
||||||
add-shared
|
add-shared
|
||||||
(mf/use-callback
|
(mf/use-callback
|
||||||
|
|
|
@ -13,14 +13,17 @@
|
||||||
[app.common.uuid :as uuid]
|
[app.common.uuid :as uuid]
|
||||||
[app.config :as cfg]
|
[app.config :as cfg]
|
||||||
[app.main.data.dashboard :as dd]
|
[app.main.data.dashboard :as dd]
|
||||||
|
[app.main.data.messages :as dm]
|
||||||
[app.main.data.modal :as modal]
|
[app.main.data.modal :as modal]
|
||||||
[app.main.fonts :as fonts]
|
[app.main.fonts :as fonts]
|
||||||
|
[app.main.refs :as refs]
|
||||||
[app.main.store :as st]
|
[app.main.store :as st]
|
||||||
[app.main.ui.dashboard.file-menu :refer [file-menu]]
|
[app.main.ui.dashboard.file-menu :refer [file-menu]]
|
||||||
[app.main.ui.dashboard.inline-edition :refer [inline-edition]]
|
[app.main.ui.dashboard.inline-edition :refer [inline-edition]]
|
||||||
[app.main.ui.icons :as i]
|
[app.main.ui.icons :as i]
|
||||||
[app.main.worker :as wrk]
|
[app.main.worker :as wrk]
|
||||||
[app.util.dom :as dom]
|
[app.util.dom :as dom]
|
||||||
|
[app.util.dom.dnd :as dnd]
|
||||||
[app.util.i18n :as i18n :refer [t tr]]
|
[app.util.i18n :as i18n :refer [t tr]]
|
||||||
[app.util.keyboard :as kbd]
|
[app.util.keyboard :as kbd]
|
||||||
[app.util.router :as rt]
|
[app.util.router :as rt]
|
||||||
|
@ -60,17 +63,30 @@
|
||||||
|
|
||||||
(mf/defc grid-item
|
(mf/defc grid-item
|
||||||
{:wrap [mf/memo]}
|
{:wrap [mf/memo]}
|
||||||
[{:keys [id file] :as props}]
|
[{:keys [id file selected-files navigate?] :as props}]
|
||||||
(let [local (mf/use-state {:menu-open false
|
(let [local (mf/use-state {:menu-open false
|
||||||
:menu-pos nil
|
:menu-pos nil
|
||||||
:edition false})
|
:edition false})
|
||||||
locale (mf/deref i18n/locale)
|
locale (mf/deref i18n/locale)
|
||||||
menu-ref (mf/use-ref)
|
item-ref (mf/use-ref)
|
||||||
|
menu-ref (mf/use-ref)
|
||||||
|
selected? (contains? selected-files id)
|
||||||
|
|
||||||
on-menu-close
|
on-menu-close
|
||||||
(mf/use-callback
|
(mf/use-callback
|
||||||
#(swap! local assoc :menu-open false))
|
#(swap! local assoc :menu-open false))
|
||||||
|
|
||||||
|
on-select
|
||||||
|
(mf/use-callback
|
||||||
|
(mf/deps id)
|
||||||
|
(fn [event]
|
||||||
|
(dom/prevent-default event)
|
||||||
|
(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 file})))))
|
||||||
|
|
||||||
on-navigate
|
on-navigate
|
||||||
(mf/use-callback
|
(mf/use-callback
|
||||||
(mf/deps id)
|
(mf/deps id)
|
||||||
|
@ -83,6 +99,43 @@
|
||||||
qparams {:page-id (first (get-in file [:data :pages]))}]
|
qparams {:page-id (first (get-in file [:data :pages]))}]
|
||||||
(st/emit! (rt/nav :workspace pparams qparams)))))))
|
(st/emit! (rt/nav :workspace pparams qparams)))))))
|
||||||
|
|
||||||
|
create-counter
|
||||||
|
(mf/use-callback
|
||||||
|
(fn [element file-count]
|
||||||
|
(let [counter-el (dom/create-element "div")]
|
||||||
|
(dom/set-property! counter-el "class" "drag-counter")
|
||||||
|
(dom/set-text! counter-el (str file-count))
|
||||||
|
counter-el)))
|
||||||
|
|
||||||
|
on-drag-start
|
||||||
|
(mf/use-callback
|
||||||
|
(mf/deps selected-files)
|
||||||
|
(fn [event]
|
||||||
|
(let [offset (dom/get-offset-position (.-nativeEvent event))
|
||||||
|
|
||||||
|
select-current? (not (contains? selected-files (:id file)))
|
||||||
|
|
||||||
|
item-el (mf/ref-val item-ref)
|
||||||
|
counter-el (create-counter 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 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 item-el counter-el)))))
|
||||||
|
|
||||||
on-menu-click
|
on-menu-click
|
||||||
(mf/use-callback
|
(mf/use-callback
|
||||||
(mf/deps file)
|
(mf/deps file)
|
||||||
|
@ -108,7 +161,13 @@
|
||||||
:edition true
|
:edition true
|
||||||
:menu-open false)))]
|
:menu-open false)))]
|
||||||
|
|
||||||
[:div.grid-item.project-th {:on-click on-navigate
|
[:div.grid-item.project-th {:class (dom/classnames
|
||||||
|
:selected selected?)
|
||||||
|
:ref item-ref
|
||||||
|
:draggable true
|
||||||
|
:on-click on-select
|
||||||
|
:on-double-click on-navigate
|
||||||
|
:on-drag-start on-drag-start
|
||||||
:on-context-menu on-menu-click}
|
:on-context-menu on-menu-click}
|
||||||
[:div.overlay]
|
[:div.overlay]
|
||||||
[:& grid-item-thumbnail {:file file}]
|
[:& grid-item-thumbnail {:file file}]
|
||||||
|
@ -130,14 +189,18 @@
|
||||||
:show? (:menu-open @local)
|
:show? (:menu-open @local)
|
||||||
:left (:x (:menu-pos @local))
|
:left (:x (:menu-pos @local))
|
||||||
:top (:y (:menu-pos @local))
|
:top (:y (:menu-pos @local))
|
||||||
|
:navigate? navigate?
|
||||||
:on-edit on-edit
|
:on-edit on-edit
|
||||||
:on-menu-close on-menu-close}]]]]))
|
:on-menu-close on-menu-close}]]]]))
|
||||||
|
|
||||||
(mf/defc empty-placeholder
|
(mf/defc empty-placeholder
|
||||||
[]
|
[{:keys [dragging?] :as props}]
|
||||||
[:div.grid-empty-placeholder
|
(if-not dragging?
|
||||||
[:div.icon i/file-html]
|
[:div.grid-empty-placeholder
|
||||||
[:div.text (tr "dashboard.empty-files")]])
|
[:div.icon i/file-html]
|
||||||
|
[:div.text (tr "dashboard.empty-files")]]
|
||||||
|
[:div.grid-row.no-wrap
|
||||||
|
[:div.grid-item]]))
|
||||||
|
|
||||||
(mf/defc loading-placeholder
|
(mf/defc loading-placeholder
|
||||||
[]
|
[]
|
||||||
|
@ -147,7 +210,8 @@
|
||||||
|
|
||||||
(mf/defc grid
|
(mf/defc grid
|
||||||
[{:keys [id opts files] :as props}]
|
[{:keys [id opts files] :as props}]
|
||||||
(let [locale (mf/deref i18n/locale)]
|
(let [locale (mf/deref i18n/locale)
|
||||||
|
selected-files (mf/deref refs/dashboard-selected-files)]
|
||||||
[:section.dashboard-grid
|
[:section.dashboard-grid
|
||||||
(cond
|
(cond
|
||||||
(nil? files)
|
(nil? files)
|
||||||
|
@ -159,73 +223,123 @@
|
||||||
[:& grid-item
|
[:& grid-item
|
||||||
{:id (:id item)
|
{:id (:id item)
|
||||||
:file item
|
:file item
|
||||||
:key (:id item)}])]
|
:selected-files selected-files
|
||||||
|
:key (:id item)
|
||||||
|
:navigate? true}])]
|
||||||
|
|
||||||
:else
|
:else
|
||||||
[:& empty-placeholder])]))
|
[:& empty-placeholder])]))
|
||||||
|
|
||||||
(mf/defc line-grid-row
|
(mf/defc line-grid-row
|
||||||
[{:keys [locale files on-load-more] :as props}]
|
[{:keys [locale files team-id selected-files on-load-more dragging?] :as props}]
|
||||||
(let [rowref (mf/use-ref)
|
(let [rowref (mf/use-ref)
|
||||||
|
|
||||||
width (mf/use-state 900)
|
width (mf/use-state nil)
|
||||||
limit (mf/use-state 1)
|
|
||||||
|
|
||||||
itemsize 290]
|
itemsize 290
|
||||||
|
ratio (if (some? @width) (/ @width itemsize) 0)
|
||||||
|
nitems (mth/floor ratio)
|
||||||
|
limit (if (and (some? @width)
|
||||||
|
(> (* itemsize (count files)) @width)
|
||||||
|
(< (- ratio nitems) 0.51))
|
||||||
|
(dec nitems) ;; Leave space for the "show all" block
|
||||||
|
nitems)
|
||||||
|
limit (if dragging?
|
||||||
|
(dec limit)
|
||||||
|
limit)
|
||||||
|
limit (max 1 limit)]
|
||||||
|
|
||||||
(mf/use-layout-effect
|
(mf/use-effect
|
||||||
(mf/deps width)
|
(fn []
|
||||||
(fn []
|
(let [node (mf/ref-val rowref)
|
||||||
(let [node (mf/ref-val rowref)
|
obs (new js/ResizeObserver
|
||||||
obs (new js/ResizeObserver
|
(fn [entries x]
|
||||||
(fn [entries x]
|
(ts/raf #(let [row (first entries)
|
||||||
(ts/raf (fn []
|
row-rect (.-contentRect ^js row)
|
||||||
(let [data (first entries)
|
row-width (.-width ^js row-rect)]
|
||||||
rect (.-contentRect ^js data)]
|
(reset! width row-width)))))]
|
||||||
(reset! width (.-width ^js rect)))))))
|
|
||||||
|
|
||||||
nitems (/ @width itemsize)
|
(.observe ^js obs node)
|
||||||
num (mth/floor nitems)]
|
(fn []
|
||||||
|
(.disconnect ^js obs)))))
|
||||||
(.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}
|
[:div.grid-row.no-wrap {:ref rowref}
|
||||||
(for [item (take @limit files)]
|
(when dragging?
|
||||||
|
[:div.grid-item])
|
||||||
|
(for [item (take limit files)]
|
||||||
[:& grid-item
|
[:& grid-item
|
||||||
{:id (:id item)
|
{:id (:id item)
|
||||||
:file item
|
:file item
|
||||||
:key (:id item)}])
|
:selected-files selected-files
|
||||||
(when (> (count files) @limit)
|
:key (:id item)
|
||||||
|
:navigate? false}])
|
||||||
|
(when (and (> limit 0)
|
||||||
|
(> (count files) limit))
|
||||||
[:div.grid-item.placeholder {:on-click on-load-more}
|
[:div.grid-item.placeholder {:on-click on-load-more}
|
||||||
[:div.placeholder-icon i/arrow-down]
|
[:div.placeholder-icon i/arrow-down]
|
||||||
[:div.placeholder-label
|
[:div.placeholder-label
|
||||||
(t locale "dashboard.show-all-files")]])]))
|
(t locale "dashboard.show-all-files")]])]))
|
||||||
|
|
||||||
(mf/defc line-grid
|
(mf/defc line-grid
|
||||||
[{:keys [project-id opts files on-load-more] :as props}]
|
[{:keys [project-id team-id opts files on-load-more] :as props}]
|
||||||
(let [locale (mf/deref i18n/locale)]
|
(let [locale (mf/deref i18n/locale)
|
||||||
[:section.dashboard-grid
|
dragging? (mf/use-state false)
|
||||||
|
|
||||||
|
selected-files (mf/deref refs/dashboard-selected-files)
|
||||||
|
selected-project (mf/deref refs/dashboard-selected-project)
|
||||||
|
|
||||||
|
on-drag-enter
|
||||||
|
(mf/use-callback
|
||||||
|
(mf/deps selected-project)
|
||||||
|
(fn [e]
|
||||||
|
(when (dnd/has-type? e "penpot/files")
|
||||||
|
(dom/prevent-default e)
|
||||||
|
(when-not (dnd/from-child? e)
|
||||||
|
(when (not= selected-project project-id)
|
||||||
|
(reset! dragging? true))))))
|
||||||
|
|
||||||
|
on-drag-over
|
||||||
|
(mf/use-callback
|
||||||
|
(fn [e]
|
||||||
|
(when (dnd/has-type? e "penpot/files")
|
||||||
|
(dom/prevent-default e))))
|
||||||
|
|
||||||
|
on-drag-leave
|
||||||
|
(mf/use-callback
|
||||||
|
(fn [e]
|
||||||
|
(when-not (dnd/from-child? e)
|
||||||
|
(reset! dragging? false))))
|
||||||
|
|
||||||
|
on-drop
|
||||||
|
(mf/use-callback
|
||||||
|
(mf/deps files selected-files)
|
||||||
|
(fn [e]
|
||||||
|
(reset! dragging? false)
|
||||||
|
(when (not= selected-project project-id)
|
||||||
|
(let [data {:ids selected-files
|
||||||
|
:project-id project-id}
|
||||||
|
|
||||||
|
mdata {:on-success
|
||||||
|
(st/emitf (dm/success (tr "dashboard.success-move-file"))
|
||||||
|
(dd/fetch-recent-files {:team-id team-id}))}]
|
||||||
|
(st/emit! (dd/move-files (with-meta data mdata)))))))]
|
||||||
|
|
||||||
|
[:section.dashboard-grid {:on-drag-enter on-drag-enter
|
||||||
|
:on-drag-over on-drag-over
|
||||||
|
:on-drag-leave on-drag-leave
|
||||||
|
:on-drop on-drop}
|
||||||
(cond
|
(cond
|
||||||
(nil? files)
|
(nil? files)
|
||||||
[:& loading-placeholder]
|
[:& loading-placeholder]
|
||||||
|
|
||||||
(seq files)
|
(seq files)
|
||||||
[:& line-grid-row {:files files
|
[:& line-grid-row {:files files
|
||||||
|
:team-id team-id
|
||||||
|
:selected-files selected-files
|
||||||
:on-load-more on-load-more
|
:on-load-more on-load-more
|
||||||
|
:dragging? @dragging?
|
||||||
:locale locale}]
|
:locale locale}]
|
||||||
|
|
||||||
:else
|
:else
|
||||||
[:& empty-placeholder])]))
|
[:& empty-placeholder {:dragging? @dragging?}])]))
|
||||||
|
|
||||||
|
|
|
@ -148,6 +148,7 @@
|
||||||
|
|
||||||
[:& line-grid
|
[:& line-grid
|
||||||
{:project-id (:id project)
|
{:project-id (:id project)
|
||||||
|
:team-id team-id
|
||||||
:on-load-more on-nav
|
:on-load-more on-nav
|
||||||
:files files}]]))
|
:files files}]]))
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,7 @@
|
||||||
[app.main.ui.icons :as i]
|
[app.main.ui.icons :as i]
|
||||||
[app.util.avatars :as avatars]
|
[app.util.avatars :as avatars]
|
||||||
[app.util.dom :as dom]
|
[app.util.dom :as dom]
|
||||||
|
[app.util.dom.dnd :as dnd]
|
||||||
[app.util.i18n :as i18n :refer [t tr]]
|
[app.util.i18n :as i18n :refer [t tr]]
|
||||||
[app.util.keyboard :as kbd]
|
[app.util.keyboard :as kbd]
|
||||||
[app.util.object :as obj]
|
[app.util.object :as obj]
|
||||||
|
@ -42,13 +43,16 @@
|
||||||
[rumext.alpha :as mf]))
|
[rumext.alpha :as mf]))
|
||||||
|
|
||||||
(mf/defc sidebar-project
|
(mf/defc sidebar-project
|
||||||
[{:keys [item selected?] :as props}]
|
[{:keys [item team-id selected?] :as props}]
|
||||||
(let [dstate (mf/deref refs/dashboard-local)
|
(let [dstate (mf/deref refs/dashboard-local)
|
||||||
edit-id (:project-for-edit dstate)
|
selected-files (:selected-files dstate)
|
||||||
|
selected-project (:selected-project dstate)
|
||||||
|
edit-id (:project-for-edit dstate)
|
||||||
|
|
||||||
local (mf/use-state {:menu-open false
|
local (mf/use-state {:menu-open false
|
||||||
:menu-pos nil
|
:menu-pos nil
|
||||||
:edition? (= (:id item) edit-id)})
|
:edition? (= (:id item) edit-id)
|
||||||
|
:dragging? false})
|
||||||
|
|
||||||
on-click
|
on-click
|
||||||
(mf/use-callback
|
(mf/use-callback
|
||||||
|
@ -75,13 +79,56 @@
|
||||||
(mf/deps item)
|
(mf/deps item)
|
||||||
(fn [name]
|
(fn [name]
|
||||||
(st/emit! (dd/rename-project (assoc item :name name)))
|
(st/emit! (dd/rename-project (assoc item :name name)))
|
||||||
(swap! local assoc :edition? false)))]
|
(swap! local assoc :edition? false)))
|
||||||
|
|
||||||
|
on-drag-enter
|
||||||
|
(mf/use-callback
|
||||||
|
(mf/deps selected-project)
|
||||||
|
(fn [e]
|
||||||
|
(when (dnd/has-type? e "penpot/files")
|
||||||
|
(dom/prevent-default e)
|
||||||
|
(when-not (dnd/from-child? e)
|
||||||
|
(when (not= selected-project (:id item))
|
||||||
|
(swap! local assoc :dragging? true))))))
|
||||||
|
|
||||||
|
on-drag-over
|
||||||
|
(mf/use-callback
|
||||||
|
(fn [e]
|
||||||
|
(when (dnd/has-type? e "penpot/files")
|
||||||
|
(dom/prevent-default e))))
|
||||||
|
|
||||||
|
on-drag-leave
|
||||||
|
(mf/use-callback
|
||||||
|
(fn [e]
|
||||||
|
(when-not (dnd/from-child? e)
|
||||||
|
(swap! local assoc :dragging? false))))
|
||||||
|
|
||||||
|
on-drop
|
||||||
|
(mf/use-callback
|
||||||
|
(mf/deps item selected-files)
|
||||||
|
(fn [e]
|
||||||
|
(swap! local assoc :dragging? false)
|
||||||
|
(when (not= selected-project (:id item))
|
||||||
|
(let [data {:ids selected-files
|
||||||
|
:project-id (:id item)}
|
||||||
|
|
||||||
|
mdata {:on-success
|
||||||
|
(st/emitf (dm/success (tr "dashboard.success-move-file"))
|
||||||
|
(rt/nav :dashboard-files
|
||||||
|
{:team-id team-id
|
||||||
|
:project-id (:id item)}))}]
|
||||||
|
(st/emit! (dd/move-files (with-meta data mdata)))))))]
|
||||||
|
|
||||||
[:*
|
[:*
|
||||||
[:li {:on-click on-click
|
[:li {:class (if selected? "current"
|
||||||
|
(when (:dragging? @local) "dragging"))
|
||||||
|
:on-click on-click
|
||||||
:on-double-click on-edit-open
|
:on-double-click on-edit-open
|
||||||
:on-context-menu on-menu-click
|
:on-context-menu on-menu-click
|
||||||
:class (when selected? "current")}
|
:on-drag-enter on-drag-enter
|
||||||
|
:on-drag-over on-drag-over
|
||||||
|
:on-drag-leave on-drag-leave
|
||||||
|
:on-drop on-drop}
|
||||||
(if (:edition? @local)
|
(if (:edition? @local)
|
||||||
[:& inline-edition {:content (:name item)
|
[:& inline-edition {:content (:name item)
|
||||||
:on-end on-edit}]
|
:on-end on-edit}]
|
||||||
|
@ -451,6 +498,7 @@
|
||||||
{:item item
|
{:item item
|
||||||
:key (:id item)
|
:key (:id item)
|
||||||
:id (:id item)
|
:id (:id item)
|
||||||
|
:team-id (:id team)
|
||||||
:selected? (= (:id item) (:id project))}])]
|
:selected? (= (:id item) (:id project))}])]
|
||||||
[:div.sidebar-empty-placeholder
|
[:div.sidebar-empty-placeholder
|
||||||
[:span.icon i/pin]
|
[:span.icon i/pin]
|
||||||
|
|
|
@ -30,7 +30,6 @@
|
||||||
[e]
|
[e]
|
||||||
(.-target e))
|
(.-target e))
|
||||||
|
|
||||||
|
|
||||||
(defn classnames
|
(defn classnames
|
||||||
[& params]
|
[& params]
|
||||||
(assert (even? (count params)))
|
(assert (even? (count params)))
|
||||||
|
@ -233,6 +232,12 @@
|
||||||
{:pre [(blob? b)]}
|
{:pre [(blob? b)]}
|
||||||
(js/URL.createObjectURL b))
|
(js/URL.createObjectURL b))
|
||||||
|
|
||||||
|
(defn set-property! [node property value]
|
||||||
|
(.setAttribute node property value))
|
||||||
|
|
||||||
|
(defn set-text! [node text]
|
||||||
|
(set! (.-textContent node) text))
|
||||||
|
|
||||||
(defn set-css-property [node property value]
|
(defn set-css-property [node property value]
|
||||||
(.setProperty (.-style ^js node) property value))
|
(.setProperty (.-style ^js node) property value))
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue