mirror of
https://github.com/heyarne/airsonic-ui.git
synced 2026-05-07 02:33:39 +02:00
Make sortable component more reusable
This commit is contained in:
parent
96b3148a02
commit
c715e5025c
4 changed files with 98 additions and 82 deletions
|
|
@ -1,16 +1,53 @@
|
||||||
(ns airsonic-ui.components.current-queue.views
|
(ns airsonic-ui.components.current-queue.views
|
||||||
(:require [re-frame.core :refer [subscribe]]
|
(:require [re-frame.core :refer [subscribe]]
|
||||||
|
[reagent.core :as reagent]
|
||||||
|
["react-sortable-hoc" :refer [SortableHandle]]
|
||||||
|
[airsonic-ui.helpers :as helpers]
|
||||||
[airsonic-ui.views.song :as song]
|
[airsonic-ui.views.song :as song]
|
||||||
[airsonic-ui.components.sortable.views :as sortable]
|
[airsonic-ui.components.sortable.views :as sortable]
|
||||||
[airsonic-ui.routes :as r]))
|
[airsonic-ui.routes :as r]))
|
||||||
|
|
||||||
|
(defonce items (reagent/atom [1 2 3 4 5 6 7]))
|
||||||
|
|
||||||
|
(def SortHandle
|
||||||
|
(SortableHandle.
|
||||||
|
;; Alternative to r/reactify-component, which doens't convert props and hiccup,
|
||||||
|
;; is to just provide fn as component and use as-element or create-element
|
||||||
|
;; to return React elements from the component.
|
||||||
|
(fn []
|
||||||
|
(reagent/as-element [:span {:style {:WebkitTouchCallout "none"
|
||||||
|
:WebkitUserSelect "none"
|
||||||
|
:KhtmlUserSelect "none"
|
||||||
|
:MozUserSelect "none"
|
||||||
|
;; NOTE: lowercase "ms" prefix
|
||||||
|
;; https://www.andismith.com/blogs/2012/02/modernizr-prefixed/
|
||||||
|
:msUserSelect "none"
|
||||||
|
:userSelect "none"}}
|
||||||
|
"::"]))))
|
||||||
|
|
||||||
(defn current-queue []
|
(defn current-queue []
|
||||||
|
(let [is-sortable? (reagent/atom true)]
|
||||||
|
(fn []
|
||||||
[:section.section>div.container
|
[:section.section>div.container
|
||||||
[:h1.title "Current Queue"]
|
[:h1.title "Current Queue"]
|
||||||
[sortable/sortable-component]
|
[sortable/sortable-component {:container [:table.table.is-fullwidth>tbody]
|
||||||
|
:items @items
|
||||||
|
|
||||||
|
:render-item
|
||||||
|
(fn [{:keys [value]}]
|
||||||
|
[:tr
|
||||||
|
[:td "Some table cell"]
|
||||||
|
[:td (str "Value: " value)]
|
||||||
|
[:td [:a {:on-click #(swap! is-sortable? not)} (str "Is sortable: " @is-sortable?)]]
|
||||||
|
[:td (when @is-sortable?
|
||||||
|
[:> SortHandle])]])
|
||||||
|
|
||||||
|
:on-sort-end
|
||||||
|
(fn [{:keys [old-idx new-idx]}]
|
||||||
|
(swap! items helpers/vector-move old-idx new-idx))}]
|
||||||
(let [current-queue @(subscribe [:audio/current-queue])
|
(let [current-queue @(subscribe [:audio/current-queue])
|
||||||
#_#_ current-song @(subscribe [:audio/current-song])]
|
#_#_ current-song @(subscribe [:audio/current-song])]
|
||||||
(if (some? current-queue)
|
(if (some? current-queue)
|
||||||
[song/listing (:items current-queue)]
|
[song/listing (:items current-queue)]
|
||||||
[:p "You are currently not playing anything. Use the search or go to your "
|
[:p "You are currently not playing anything. Use the search or go to your "
|
||||||
[:a {:href (r/url-for ::r/library)} "Library"] " to start playing some music."]))])
|
[:a {:href (r/url-for ::r/library)} "Library"] " to start playing some music."]))])))
|
||||||
|
|
|
||||||
|
|
@ -3,58 +3,21 @@
|
||||||
[clojure.string :as str]
|
[clojure.string :as str]
|
||||||
["react-sortable-hoc" :refer [SortableHandle SortableElement
|
["react-sortable-hoc" :refer [SortableHandle SortableElement
|
||||||
SortableContainer]]))
|
SortableContainer]]))
|
||||||
|
;; this code is taken and adapted from https://github.com/reagent-project/reagent/blob/72c95257c13e5de1531e16d1a06da7686041d3f4/examples/react-sortable-hoc/src/example/core.cljs
|
||||||
|
|
||||||
(defonce state
|
(defn make-wrapper [{:keys [container render-item]}]
|
||||||
(r/atom {:items (vec (map #(str "Item " %) (range 1 11)))
|
(let [SortableItem (SortableElement.
|
||||||
:sort-enabled? true}))
|
(r/reactify-component render-item))]
|
||||||
|
|
||||||
;; this code is taken from https://github.com/reagent-project/reagent/blob/72c95257c13e5de1531e16d1a06da7686041d3f4/examples/react-sortable-hoc/src/example/core.cljs
|
|
||||||
(def DragHandle
|
|
||||||
(SortableHandle.
|
|
||||||
;; Alternative to r/reactify-component, which doens't convert props and hiccup,
|
|
||||||
;; is to just provide fn as component and use as-element or create-element
|
|
||||||
;; to return React elements from the component.
|
|
||||||
(fn []
|
|
||||||
(r/as-element [:span {:style {:WebkitTouchCallout "none"
|
|
||||||
:WebkitUserSelect "none"
|
|
||||||
:KhtmlUserSelect "none"
|
|
||||||
:MozUserSelect "none"
|
|
||||||
;; NOTE: lowercase "ms" prefix
|
|
||||||
;; https://www.andismith.com/blogs/2012/02/modernizr-prefixed/
|
|
||||||
:msUserSelect "none"
|
|
||||||
:userSelect "none"}}
|
|
||||||
"::"]))))
|
|
||||||
|
|
||||||
(def SortableRow
|
|
||||||
(SortableElement.
|
|
||||||
(r/reactify-component (fn [{:keys [value]}]
|
|
||||||
[:tr
|
|
||||||
[:td value]
|
|
||||||
(when (:sort-enabled? @state)
|
|
||||||
[:td [:> DragHandle]])]))))
|
|
||||||
|
|
||||||
(def SortableTable
|
|
||||||
(SortableContainer.
|
(SortableContainer.
|
||||||
(r/reactify-component
|
(r/reactify-component
|
||||||
(fn [{:keys [items]}]
|
(fn [{:keys [items]}]
|
||||||
[:table.table.is-fullwidth>tbody
|
(into container
|
||||||
(for [[idx value] (map-indexed vector items)]
|
(for [[idx value] (map-indexed vector items)]
|
||||||
;; No :> or adapt-react-class here because that would convert value to JS
|
|
||||||
(r/create-element
|
(r/create-element
|
||||||
SortableRow
|
SortableItem
|
||||||
#js {:key (str "item-" idx)
|
#js {:key (str "item-" idx)
|
||||||
:index idx
|
:index idx
|
||||||
:value value}))]))))
|
:value value}))))))))
|
||||||
|
|
||||||
(defn vector-move [coll prev-index new-index]
|
|
||||||
(let [items (into (subvec coll 0 prev-index)
|
|
||||||
(subvec coll (inc prev-index)))]
|
|
||||||
(-> (subvec items 0 new-index)
|
|
||||||
(conj (get coll prev-index))
|
|
||||||
(into (subvec items new-index)))))
|
|
||||||
|
|
||||||
(comment
|
|
||||||
(= [0 2 3 4 1 5] (vector-move [0 1 2 3 4 5] 1 4)))
|
|
||||||
|
|
||||||
(defn style-map
|
(defn style-map
|
||||||
"Returns a map representing all currently set css styles; this makes sense
|
"Returns a map representing all currently set css styles; this makes sense
|
||||||
|
|
@ -97,17 +60,18 @@
|
||||||
|
|
||||||
(defonce saved-snapshot (atom nil))
|
(defonce saved-snapshot (atom nil))
|
||||||
|
|
||||||
(defn sortable-component []
|
(defn sortable-component [{:keys [container items render-item on-sort-end]}]
|
||||||
|
(let [Wrapper (make-wrapper {:container container
|
||||||
|
:render-item render-item})]
|
||||||
(r/create-element
|
(r/create-element
|
||||||
SortableTable
|
Wrapper
|
||||||
#js {:items (:items @state)
|
#js {:items items
|
||||||
:helperClass "sortable-is-moving"
|
:helperClass "sortable-is-moving"
|
||||||
|
|
||||||
;; save the style of all of the rows children
|
;; save the style of all of the rows children
|
||||||
:updateBeforeSortStart
|
:updateBeforeSortStart
|
||||||
(fn [event]
|
(fn [event]
|
||||||
(reset! saved-snapshot (style-snapshot (.-node event))))
|
(reset! saved-snapshot (style-snapshot (.-node event))))
|
||||||
|
|
||||||
:onSortStart
|
:onSortStart
|
||||||
(fn [_]
|
(fn [_]
|
||||||
;; the node we get passed as parameter is the original node unfortunately
|
;; the node we get passed as parameter is the original node unfortunately
|
||||||
|
|
@ -116,5 +80,7 @@
|
||||||
;; update the state to reflect the new order
|
;; update the state to reflect the new order
|
||||||
:onSortEnd
|
:onSortEnd
|
||||||
(fn [event]
|
(fn [event]
|
||||||
(swap! state update :items vector-move (.-oldIndex event) (.-newIndex event)))
|
(on-sort-end {:old-idx (.-oldIndex event)
|
||||||
:useDragHandle true}))
|
:new-idx (.-newIndex event)}))
|
||||||
|
|
||||||
|
:useDragHandle true})))
|
||||||
|
|
|
||||||
|
|
@ -55,3 +55,10 @@
|
||||||
(if brief?
|
(if brief?
|
||||||
(brief-duration hours minutes seconds)
|
(brief-duration hours minutes seconds)
|
||||||
(long-duration hours minutes seconds))))
|
(long-duration hours minutes seconds))))
|
||||||
|
|
||||||
|
(defn vector-move [coll prev-index new-index]
|
||||||
|
(let [items (into (subvec coll 0 prev-index)
|
||||||
|
(subvec coll (inc prev-index)))]
|
||||||
|
(-> (subvec items 0 new-index)
|
||||||
|
(conj (get coll prev-index))
|
||||||
|
(into (subvec items new-index)))))
|
||||||
|
|
|
||||||
|
|
@ -43,3 +43,9 @@
|
||||||
(is (= "59:00" (helpers/format-duration (* 59 60) :brief? true)))
|
(is (= "59:00" (helpers/format-duration (* 59 60) :brief? true)))
|
||||||
(is (= "01:00" (helpers/format-duration 60 :brief? true)))
|
(is (= "01:00" (helpers/format-duration 60 :brief? true)))
|
||||||
(is (= "00:05" (helpers/format-duration 5 :brief? true)))))
|
(is (= "00:05" (helpers/format-duration 5 :brief? true)))))
|
||||||
|
|
||||||
|
(deftest vector-move
|
||||||
|
(testing "Should correctly move items in a vector"
|
||||||
|
(is (= [1] (helpers/vector-move [1] 0 0)))
|
||||||
|
(is (= [2 1] (helpers/vector-move [1 2] 0 1) (helpers/vector-move [1 2] 1 0)))
|
||||||
|
(is (= [1 4 2 3 5] (helpers/vector-move [1 2 3 4 5] 3 1)))))
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue