Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Recipe: drag-n-drop system for lists of files #6

Open
holyjak opened this issue Sep 6, 2023 · 0 comments
Open

Recipe: drag-n-drop system for lists of files #6

holyjak opened this issue Sep 6, 2023 · 0 comments

Comments

@holyjak
Copy link
Contributor

holyjak commented Sep 6, 2023

Uses a js npm library to do most of the work, so this is really just an “integration” demo.

(ns app.ui.sortable
  (:require
    [com.fulcrologic.fulcro.algorithms.react-interop :as op]
    ["@dnd-kit/core" :refer [DndContext closestCenter PointerSensor KeyboardSensor useSensor useSensors]]
    ["@dnd-kit/sortable" :refer [arrayMove SortableContext sortableKeyboardCoordinates verticalListSortingStrategy useSortable]]
    ["@dnd-kit/utilities" :refer [CSS]]
    [com.fulcrologic.fulcro.components :as comp :refer [defsc]]
    [com.fulcrologic.fulcro.dom :refer [div]]
    [taoensso.timbre :as log]))

(defn use-sortable [options] (js->clj
                               (useSortable (clj->js options))
                               :keywordize-keys true))
(defn use-sensor
  "Wrapper for useSensor"
  ([s] (useSensor s))
  ([s options] (useSensor s (clj->js options))))
(defn use-sensors "Wrapper for useSensors:"
  [& sensors] (apply useSensors sensors))
(def ui-dnd-context (op/react-factory DndContext))
(def ui-sortable-context (op/react-factory SortableContext))

(defsc SortableItem [this {:keys [id label render-item] :as props}]
  {:use-hooks? true}
  (when (and goog.DEBUG (not (string? id)))
    (log/warn "Sortable Item requires a STRING id. You passed: " id ", a " (type id)))
  (let [{:keys [attributes listeners setNodeRef transform transition]} (use-sortable {:id id})
        css-style {:transform  (.toString (.-Transform CSS) (clj->js transform))
                   :transition transition}
        props     (merge
                    attributes
                    listeners
                    {:style css-style
                     :ref   setNodeRef})]
    ((or render-item div) props label)))

(def ui-sortable-item (comp/factory SortableItem {:keyfn :id}))

(defsc SortableList [this {:keys [get-item-order get-item-label get-item-id items onSort render-item]}]
  {:use-hooks? true}
  (let [items        (vec (sort-by get-item-order items))
        item-ids     (clj->js (mapv get-item-id items))
        sensors      (use-sensors
                       (use-sensor PointerSensor)
                       (use-sensor KeyboardSensor {:coordinateGetter sortableKeyboardCoordinates}))
        update-order (fn [^js evt]
                       (let [sid-moved       (.. evt -active -id)
                             sid-over        (.. evt -over -id)
                             old-index       (.indexOf item-ids sid-moved)
                             new-index       (.indexOf item-ids sid-over)
                             sorted-item-ids (arrayMove item-ids old-index new-index)
                             id->item        (zipmap (map get-item-id items) items)
                             sorted-items (mapv id->item sorted-item-ids)]
                         (when onSort (onSort sorted-items))))]
    (ui-dnd-context {:sensors            sensors
                     :collisionDetection closestCenter
                     :onDragEnd          update-order}
      (ui-sortable-context {:strategy verticalListSortingStrategy
                            :items    item-ids}
        (mapv
          (fn [item]
            (ui-sortable-item {:id          (get-item-id item)
                               :render-item render-item
                               :label       (get-item-label item)}))
          items)))))

(def ui-sortable-list
  "[props]

   Render the DnD context, sortable context, and sortable items for dnd-kit. Does not wrap with any kind of list, so you
   should do that externally. You can optionally specify `:render-item` to control the rendering of the specific item, but
   you must be sure to include the `element-props` (which include CSS style for moving the element) in whatever top-level
   dom element you return.

   Props:

   * :get-item-order - A (fn [item] sort-index) for the order of the item
   * :get-item-label - A (fn [item] string-label) to label the item.
   * :get-item-id    - A (fn [item] string-id) to identify the item. You must convert the item id to a string
   * :render-item    - (optional) A (fn [element-props label] react-element) to use to render the item. A div is used by default.
   * :onSort         - A (fn [sorted-items]) that can side-effect to update the data of items so that they are properly sorted.
                       These will be the actual items passed in :items, but in their new preferred order.
   * :items          - The list of items.
  "
  (comp/factory SortableList))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant