mirror of
https://github.com/penpot/penpot.git
synced 2025-03-12 15:51:37 -05:00
🎉 Add page ordering (with d&d).
This commit is contained in:
parent
11f54f51ea
commit
04d364225c
7 changed files with 91 additions and 51 deletions
|
@ -11,6 +11,7 @@
|
||||||
(:require
|
(:require
|
||||||
[clojure.spec.alpha :as s]
|
[clojure.spec.alpha :as s]
|
||||||
[promesa.core :as p]
|
[promesa.core :as p]
|
||||||
|
[uxbox.common.data :as d]
|
||||||
[uxbox.common.pages :as cp]
|
[uxbox.common.pages :as cp]
|
||||||
[uxbox.common.exceptions :as ex]
|
[uxbox.common.exceptions :as ex]
|
||||||
[uxbox.common.spec :as us]
|
[uxbox.common.spec :as us]
|
||||||
|
@ -106,6 +107,33 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
;; --- Mutation: Sort Pages
|
||||||
|
|
||||||
|
(s/def ::page-ids (s/every ::us/uuid :kind vector?))
|
||||||
|
(s/def ::reorder-pages
|
||||||
|
(s/keys :req-un [::profile-id ::file-id ::page-ids]))
|
||||||
|
|
||||||
|
(declare update-page-ordering)
|
||||||
|
|
||||||
|
(sm/defmutation ::reorder-pages
|
||||||
|
[{:keys [profile-id file-id page-ids]}]
|
||||||
|
(db/with-atomic [conn db/pool]
|
||||||
|
(p/run! #(update-page-ordering conn file-id %)
|
||||||
|
(d/enumerate page-ids))
|
||||||
|
nil))
|
||||||
|
|
||||||
|
(def ^:private sql:update-page-ordering
|
||||||
|
"update page
|
||||||
|
set ordering = $1
|
||||||
|
where id = $2 and file_id = $3")
|
||||||
|
|
||||||
|
(defn- update-page-ordering
|
||||||
|
[conn file-id [ordering page-id]]
|
||||||
|
(-> (db/query-one conn [sql:update-page-ordering ordering page-id file-id])
|
||||||
|
(p/then su/constantly-nil)))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
;; --- Mutation: Generate Share Token
|
;; --- Mutation: Generate Share Token
|
||||||
|
|
||||||
(declare assign-page-share-token)
|
(declare assign-page-share-token)
|
||||||
|
|
|
@ -93,7 +93,7 @@
|
||||||
and (fp_r.is_admin = true or
|
and (fp_r.is_admin = true or
|
||||||
fp_r.is_owner = true or
|
fp_r.is_owner = true or
|
||||||
fp_r.can_edit = true)
|
fp_r.can_edit = true)
|
||||||
window pages_w as (partition by f.id order by pg.created_at
|
window pages_w as (partition by f.id order by pg.ordering
|
||||||
range between unbounded preceding
|
range between unbounded preceding
|
||||||
and unbounded following)
|
and unbounded following)
|
||||||
order by f.modified_at desc")
|
order by f.modified_at desc")
|
||||||
|
@ -182,7 +182,7 @@
|
||||||
where f.id = $1
|
where f.id = $1
|
||||||
and f.deleted_at is null
|
and f.deleted_at is null
|
||||||
and pg.deleted_at is null
|
and pg.deleted_at is null
|
||||||
window pages_w as (partition by f.id order by pg.created_at
|
window pages_w as (partition by f.id order by pg.ordering
|
||||||
range between unbounded preceding
|
range between unbounded preceding
|
||||||
and unbounded following)")
|
and unbounded following)")
|
||||||
|
|
||||||
|
@ -229,22 +229,6 @@
|
||||||
(check-edition-permissions! conn profile-id id)
|
(check-edition-permissions! conn profile-id id)
|
||||||
(retrieve-file conn id)))
|
(retrieve-file conn id)))
|
||||||
|
|
||||||
;; --- Query: Project Files
|
|
||||||
|
|
||||||
;; (declare retrieve-project-files)
|
|
||||||
|
|
||||||
;; (s/def ::project-files
|
|
||||||
;; (s/keys :req-un [::profile-id]
|
|
||||||
;; :opt-un [::project-id]))
|
|
||||||
|
|
||||||
;; (sq/defquery ::project-files
|
|
||||||
;; [{:keys [project-id] :as params}]
|
|
||||||
;; (retrieve-project-files db/pool params))
|
|
||||||
|
|
||||||
;; (defn retrieve-project-files
|
|
||||||
;; [conn {:keys [profile-id project-id]}]
|
|
||||||
;; (-> (db/query conn [sql:project-files profile-id project-id])
|
|
||||||
;; (p/then' (partial mapv decode-row))))
|
|
||||||
|
|
||||||
;; --- Helpers
|
;; --- Helpers
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,7 @@
|
||||||
and (fp_r.is_admin = true or
|
and (fp_r.is_admin = true or
|
||||||
fp_r.is_owner = true or
|
fp_r.is_owner = true or
|
||||||
fp_r.can_edit = true)
|
fp_r.can_edit = true)
|
||||||
window pages_w as (partition by f.id order by pg.created_at
|
window pages_w as (partition by f.id order by pg.ordering
|
||||||
range between unbounded preceding
|
range between unbounded preceding
|
||||||
and unbounded following)
|
and unbounded following)
|
||||||
order by f.modified_at desc
|
order by f.modified_at desc
|
||||||
|
|
|
@ -1326,6 +1326,28 @@
|
||||||
[]
|
[]
|
||||||
{:commit-local? true}))))))
|
{:commit-local? true}))))))
|
||||||
|
|
||||||
|
;; --- Change Page Order (D&D Ordering)
|
||||||
|
|
||||||
|
(defn relocate-page
|
||||||
|
[id index]
|
||||||
|
(ptk/reify ::relocate-pages
|
||||||
|
ptk/UpdateEvent
|
||||||
|
(update [_ state]
|
||||||
|
(let [pages (get-in state [:workspace-file :pages])
|
||||||
|
[before after] (split-at index pages)
|
||||||
|
p? (partial = id)
|
||||||
|
pages' (d/concat []
|
||||||
|
(remove p? before)
|
||||||
|
[id]
|
||||||
|
(remove p? after))]
|
||||||
|
(assoc-in state [:workspace-file :pages] pages')))
|
||||||
|
|
||||||
|
ptk/WatchEvent
|
||||||
|
(watch [_ state stream]
|
||||||
|
(let [file (:workspace-file state)]
|
||||||
|
(->> (rp/mutation! :reorder-pages {:page-ids (:pages file)
|
||||||
|
:file-id (:id file)})
|
||||||
|
(rx/ignore))))))
|
||||||
|
|
||||||
;; --- Shape / Selection Alignment and Distribution
|
;; --- Shape / Selection Alignment and Distribution
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
(ns uxbox.main.ui.dashboard.grid
|
(ns uxbox.main.ui.dashboard.grid
|
||||||
(:refer-clojure :exclude [sort-by])
|
|
||||||
(:require
|
(:require
|
||||||
[cuerdas.core :as str]
|
[cuerdas.core :as str]
|
||||||
[rumext.alpha :as mf]
|
[rumext.alpha :as mf]
|
||||||
|
@ -11,7 +10,6 @@
|
||||||
[uxbox.main.ui.keyboard :as kbd]
|
[uxbox.main.ui.keyboard :as kbd]
|
||||||
[uxbox.main.ui.confirm :refer [confirm-dialog]]
|
[uxbox.main.ui.confirm :refer [confirm-dialog]]
|
||||||
[uxbox.main.ui.components.context-menu :refer [context-menu]]
|
[uxbox.main.ui.components.context-menu :refer [context-menu]]
|
||||||
[uxbox.util.data :refer [classnames]]
|
|
||||||
[uxbox.util.dom :as dom]
|
[uxbox.util.dom :as dom]
|
||||||
[uxbox.util.i18n :as i18n :refer [t tr]]
|
[uxbox.util.i18n :as i18n :refer [t tr]]
|
||||||
[uxbox.util.router :as rt]
|
[uxbox.util.router :as rt]
|
||||||
|
@ -75,7 +73,7 @@
|
||||||
:default-value (:name file)}]
|
:default-value (:name file)}]
|
||||||
[:h3 (:name file)])
|
[:h3 (:name file)])
|
||||||
[:& grid-item-metadata {:modified-at (:modified-at file)}]]
|
[:& grid-item-metadata {:modified-at (:modified-at file)}]]
|
||||||
[:div.project-th-actions {:class (classnames
|
[:div.project-th-actions {:class (dom/classnames
|
||||||
:force-display (:menu-open @local))}
|
:force-display (:menu-open @local))}
|
||||||
;; [:div.project-th-icon.pages
|
;; [:div.project-th-icon.pages
|
||||||
;; i/page
|
;; i/page
|
||||||
|
|
|
@ -162,7 +162,6 @@
|
||||||
]
|
]
|
||||||
[:li {:on-context-menu on-context-menu
|
[:li {:on-context-menu on-context-menu
|
||||||
:ref dref
|
:ref dref
|
||||||
:data-index index
|
|
||||||
:class (dom/classnames
|
:class (dom/classnames
|
||||||
:dnd-over-top (= (:over dprops) :top)
|
:dnd-over-top (= (:over dprops) :top)
|
||||||
:dnd-over-bot (= (:over dprops) :bot)
|
:dnd-over-bot (= (:over dprops) :bot)
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
[cuerdas.core :as str]
|
[cuerdas.core :as str]
|
||||||
[okulary.core :as l]
|
[okulary.core :as l]
|
||||||
[rumext.alpha :as mf]
|
[rumext.alpha :as mf]
|
||||||
|
[uxbox.common.data :as d]
|
||||||
[uxbox.builtins.icons :as i]
|
[uxbox.builtins.icons :as i]
|
||||||
[uxbox.main.data.workspace :as dw]
|
[uxbox.main.data.workspace :as dw]
|
||||||
[uxbox.main.store :as st]
|
[uxbox.main.store :as st]
|
||||||
|
@ -17,7 +18,7 @@
|
||||||
[uxbox.main.ui.confirm :refer [confirm-dialog]]
|
[uxbox.main.ui.confirm :refer [confirm-dialog]]
|
||||||
[uxbox.main.ui.keyboard :as kbd]
|
[uxbox.main.ui.keyboard :as kbd]
|
||||||
[uxbox.main.ui.modal :as modal]
|
[uxbox.main.ui.modal :as modal]
|
||||||
[uxbox.util.data :refer [classnames enumerate]]
|
[uxbox.main.ui.hooks :as hooks]
|
||||||
[uxbox.util.dom :as dom]
|
[uxbox.util.dom :as dom]
|
||||||
[uxbox.util.i18n :as i18n :refer [t]]
|
[uxbox.util.i18n :as i18n :refer [t]]
|
||||||
[uxbox.util.router :as rt]))
|
[uxbox.util.router :as rt]))
|
||||||
|
@ -27,6 +28,7 @@
|
||||||
(mf/defc page-item
|
(mf/defc page-item
|
||||||
[{:keys [page index deletable? selected?] :as props}]
|
[{:keys [page index deletable? selected?] :as props}]
|
||||||
(let [local (mf/use-state {})
|
(let [local (mf/use-state {})
|
||||||
|
|
||||||
on-double-click
|
on-double-click
|
||||||
(fn [event]
|
(fn [event]
|
||||||
(dom/prevent-default event)
|
(dom/prevent-default event)
|
||||||
|
@ -54,22 +56,27 @@
|
||||||
(dom/stop-propagation %)
|
(dom/stop-propagation %)
|
||||||
(modal/show! confirm-dialog {:on-accept delete-fn}))
|
(modal/show! confirm-dialog {:on-accept delete-fn}))
|
||||||
|
|
||||||
on-drop #(do (prn "TODO"))
|
|
||||||
on-hover #(st/emit! (dw/change-page-order {:id (:id page)
|
|
||||||
:index index}))
|
|
||||||
|
|
||||||
navigate-fn #(st/emit! (dw/go-to-page (:id page)))
|
navigate-fn #(st/emit! (dw/go-to-page (:id page)))
|
||||||
;; [dprops ref] (use-sortable {:type "page-item"
|
|
||||||
;; :data {:id (:id page)
|
on-drop
|
||||||
;; :index index}
|
(fn [side {:keys [id name] :as data}]
|
||||||
;; :on-hover on-hover
|
(let [index (if (= :bot side) (inc index) index)]
|
||||||
;; :on-drop on-drop})
|
(st/emit! (dw/relocate-page id index))))
|
||||||
]
|
|
||||||
[:li {:class (classnames :selected selected?)}
|
[dprops dref] (hooks/use-sortable
|
||||||
[:div.element-list-body {:class (classnames
|
:type "page"
|
||||||
|
:on-drop on-drop
|
||||||
|
:data {:id (:id page)
|
||||||
|
:index index
|
||||||
|
:name (:name page)})]
|
||||||
|
|
||||||
|
[:li {:class (dom/classnames
|
||||||
:selected selected?
|
:selected selected?
|
||||||
;; :dragging (:dragging? dprops)
|
:dnd-over-top (= (:over dprops) :top)
|
||||||
)
|
:dnd-over-bot (= (:over dprops) :bot))
|
||||||
|
:ref dref}
|
||||||
|
[:div.element-list-body {:class (dom/classnames
|
||||||
|
:selected selected?)
|
||||||
:on-click navigate-fn
|
:on-click navigate-fn
|
||||||
:on-double-click on-double-click}
|
:on-double-click on-double-click}
|
||||||
[:div.page-icon i/file-html]
|
[:div.page-icon i/file-html]
|
||||||
|
@ -89,16 +96,17 @@
|
||||||
|
|
||||||
;; --- Page Item Wrapper
|
;; --- Page Item Wrapper
|
||||||
|
|
||||||
(defn- make-page-ref
|
(defn- make-page-iref
|
||||||
[id]
|
[id]
|
||||||
#(-> (l/in [:workspace-pages id])
|
#(l/derived (fn [state]
|
||||||
(l/derived st/state)))
|
(let [page (get-in state [:workspace-pages id])]
|
||||||
|
(select-keys page [:id :name :ordering])))
|
||||||
|
st/state =))
|
||||||
|
|
||||||
(mf/defc page-item-wrapper
|
(mf/defc page-item-wrapper
|
||||||
[{:keys [page-id index deletable? selected?] :as props}]
|
[{:keys [page-id index deletable? selected?] :as props}]
|
||||||
(let [page-iref (mf/use-memo
|
(let [page-iref (mf/use-memo (mf/deps page-id)
|
||||||
(mf/deps page-id)
|
(make-page-iref page-id))
|
||||||
(make-page-ref page-id))
|
|
||||||
page (mf/deref page-iref)]
|
page (mf/deref page-iref)]
|
||||||
[:& page-item {:page page
|
[:& page-item {:page page
|
||||||
:index index
|
:index index
|
||||||
|
@ -109,11 +117,12 @@
|
||||||
|
|
||||||
(mf/defc pages-list
|
(mf/defc pages-list
|
||||||
[{:keys [file current-page] :as props}]
|
[{:keys [file current-page] :as props}]
|
||||||
(let [pages (enumerate (:pages file))
|
(let [pages (d/enumerate (:pages file))
|
||||||
deletable? (> (count pages) 1)]
|
deletable? (> (count pages) 1)]
|
||||||
[:ul.element-list
|
[:ul.element-list
|
||||||
(for [[index page-id] pages]
|
(for [[index page-id] pages]
|
||||||
[:& page-item-wrapper {:page-id page-id
|
[:& page-item-wrapper
|
||||||
|
{:page-id page-id
|
||||||
:index index
|
:index index
|
||||||
:deletable? deletable?
|
:deletable? deletable?
|
||||||
:selected? (= page-id (:id current-page))
|
:selected? (= page-id (:id current-page))
|
||||||
|
|
Loading…
Add table
Reference in a new issue