Skip to content

Instantly share code, notes, and snippets.

@dborisenko
Created March 6, 2018 13:18
Show Gist options
  • Select an option

  • Save dborisenko/0303839c089a7dc25acd2418f2bea8c4 to your computer and use it in GitHub Desktop.

Select an option

Save dborisenko/0303839c089a7dc25acd2418f2bea8c4 to your computer and use it in GitHub Desktop.
package com.dbrsn.sortable
import scala.collection.mutable.ListBuffer
/**
* A change in index of an item generated by dragging
*
* @param oldIndex The item's old index
* @param newIndex The item's new index
*/
case class IndexChange(oldIndex: Int, newIndex: Int) {
def updatedList[A](l: List[A]): List[A] = {
val lb = ListBuffer(l: _*)
val e = lb.remove(oldIndex)
lb.insert(newIndex, e)
lb.toList
}
}
package com.dbrsn.sortable
import japgolly.scalajs.react.{ Callback, Children, GenericComponent, JsComponent }
import scala.language.higherKinds
import scala.scalajs.js
object SortableContainer {
@js.native
protected trait Permutation extends js.Object {
val oldIndex: Int = js.native
val newIndex: Int = js.native
}
@js.native
trait Props extends js.Object {
val axis: js.UndefOr[String] = js.native
val lockAxis: js.UndefOr[String] = js.native
val helperClass: js.UndefOr[String] = js.native
val transitionDuration: js.UndefOr[Int] = js.native
val pressDelay: js.UndefOr[Int] = js.native
val distance: js.UndefOr[Int] = js.native
val useDragHandle: js.UndefOr[Boolean] = js.native
val useWindowAsScrollContainer: js.UndefOr[Boolean] = js.native
val hideSortableGhost: js.UndefOr[Boolean] = js.native
val lockToContainerEdges: js.UndefOr[Boolean] = js.native
//Note this function actually gets "{oldIndex, newIndex, collection}, e", but we don't have much use for the other arguments
val onSortEnd: js.Function1[Permutation, Unit] = js.native
}
object Props {
def apply(
axis: js.UndefOr[String] = js.undefined,
lockAxis: js.UndefOr[String] = js.undefined,
helperClass: js.UndefOr[String] = SortableView.HandleClassName,
transitionDuration: js.UndefOr[Int] = js.undefined,
pressDelay: js.UndefOr[Int] = js.undefined,
distance: js.UndefOr[Int] = js.undefined,
useDragHandle: js.UndefOr[Boolean] = js.undefined,
useWindowAsScrollContainer: js.UndefOr[Boolean] = js.undefined,
hideSortableGhost: js.UndefOr[Boolean] = js.undefined,
lockToContainerEdges: js.UndefOr[Boolean] = js.undefined,
//Note this function actually gets "{oldIndex, newIndex, collection}, e", but we don't have much use for the other arguments
onSortEnd: IndexChange => Callback = _ => Callback.empty
): Props =
js.Dynamic.literal(
axis = axis, lockAxis = lockAxis, helperClass = helperClass, transitionDuration = transitionDuration, pressDelay = pressDelay,
distance = distance, useDragHandle = useDragHandle, useWindowAsScrollContainer = useWindowAsScrollContainer,
hideSortableGhost = hideSortableGhost, lockToContainerEdges = lockToContainerEdges,
onSortEnd = js.defined { p: Permutation => onSortEnd(IndexChange(p.oldIndex, p.newIndex)).runNow() }
).asInstanceOf[Props]
}
/**
* Wrap another component
*
* @param wrappedComponent The wrapped component itself
* @tparam P The type of Props of the wrapped component
* @return A component wrapping the wrapped component...
*/
def apply[P, CT[_, _]](wrappedComponent: GenericComponent[P, CT, _]): Props => P => JsComponent.Unmounted[js.Object, Null] = { (props) => (wrappedProps) => {
val reactElement = SortableHOC.SortableContainer(wrappedComponent.raw)
val component = JsComponent[js.Object, Children.None, Null](reactElement)
val mergedProps = js.Dynamic.literal()
mergedProps.updateDynamic("axis")(props.axis)
mergedProps.updateDynamic("lockAxis")(props.lockAxis)
mergedProps.updateDynamic("helperClass")(props.helperClass)
mergedProps.updateDynamic("transitionDuration")(props.transitionDuration)
mergedProps.updateDynamic("pressDelay")(props.pressDelay)
mergedProps.updateDynamic("distance")(props.distance)
mergedProps.updateDynamic("useDragHandle")(props.useDragHandle)
mergedProps.updateDynamic("useWindowAsScrollContainer")(props.useWindowAsScrollContainer)
mergedProps.updateDynamic("hideSortableGhost")(props.hideSortableGhost)
mergedProps.updateDynamic("lockToContainerEdges")(props.lockToContainerEdges)
mergedProps.updateDynamic("onSortEnd")(props.onSortEnd)
mergedProps.updateDynamic("a")(wrappedProps.asInstanceOf[js.Any])
component(mergedProps.asInstanceOf[js.Object])
}
}
}
package com.dbrsn.sortable
import japgolly.scalajs.react.{ Children, GenericComponent, JsComponent }
import scala.language.higherKinds
import scala.scalajs.js
object SortableElement {
@js.native
trait Props extends js.Object {
var index: Int = js.native
var collection: Int = js.native
var disabled: Boolean = js.native
}
object Props {
def apply(
index: Int,
collection: Int = 0,
disabled: Boolean = false
): Props =
js.Dynamic.literal(index = index, collection = collection, disabled = disabled).asInstanceOf[Props]
}
/**
* Wrap another component
*
* @param wrappedComponent The wrapped component itself
* @tparam P The type of Props of the wrapped component
* @return A component wrapping the wrapped component...
*/
def apply[P, CT[_, _]](wrappedComponent: GenericComponent[P, CT, _]): Props => P => JsComponent.Unmounted[js.Object, Null] = { (props) => (wrappedProps) => {
val reactElement = SortableHOC.SortableElement(wrappedComponent.raw)
val component = JsComponent[js.Object, Children.None, Null](reactElement)
val mergedProps = js.Dynamic.literal()
mergedProps.updateDynamic("index")(props.index)
mergedProps.updateDynamic("collection")(props.collection)
mergedProps.updateDynamic("disabled")(props.disabled)
mergedProps.updateDynamic("a")(wrappedProps.asInstanceOf[js.Any])
component(mergedProps.asInstanceOf[js.Object])
}
}
}
package com.dbrsn.sortable
import japgolly.scalajs.react._
import scala.language.higherKinds
import scala.scalajs.js
object SortableHandle {
/**
* Wrap another component
*
* @param wrappedComponent The wrapped component itself
* @tparam P The type of Props of the wrapped component
* @return A component wrapping the wrapped component
*/
def apply[P, CT[_, _]](wrappedComponent: GenericComponent[P, CT, _]): P => JsComponent.Unmounted[js.Object, Null] = {
(wrappedProps) =>
{
val reactElement = SortableHOC.SortableHandle(wrappedComponent.raw)
val component = JsComponent[js.Object, Children.None, Null](reactElement)
val mergedProps = js.Dynamic.literal()
mergedProps.updateDynamic("a")(wrappedProps.asInstanceOf[js.Any])
component(mergedProps.asInstanceOf[js.Object])
}
}
}
package com.dbrsn.sortable
import japgolly.scalajs.react.component.Generic.ComponentRaw
import japgolly.scalajs.react.raw.ReactElement
import scala.scalajs.js
import scala.scalajs.js.annotation.JSImport
@js.native
@JSImport("react-sortable-hoc", JSImport.Namespace)
private[sortable] object SortableHOC extends js.Object {
def SortableContainer(component: ComponentRaw#Raw): ReactElement = js.native
def SortableElement(component: ComponentRaw#Raw): ReactElement = js.native
def SortableHandle(component: ComponentRaw#Raw): ReactElement = js.native
}
package com.dbrsn.sortable
import japgolly.scalajs.react._
import japgolly.scalajs.react.component.Js.Unmounted
import japgolly.scalajs.react.vdom.html_<^._
import scala.scalajs.js
object SortableView {
import japgolly.scalajs.react.vdom.SvgAttrs._
import japgolly.scalajs.react.vdom.SvgTags._
final val HandleClassName: String = "react-sortable-handle"
final case class Props(
handleClassName: String = HandleClassName,
color: String = "#A0A0A0",
height: Int = 20
)
private val handleGrip = ScalaComponent.builder[Props]("HandleGrip")
.render_P { p =>
<.div(
^.className := p.handleClassName,
svg(
^.className := "react-sortable-handle-svg",
viewBox := "0 0 24 24",
fill := p.color,
height := p.height,
path(d := "M9,8c1.1,0,2-0.9,2-2s-0.9-2-2-2S7,4.9,7,6S7.9,8,9,8z M9,10c-1.1,0-2,0.9-2,2s0.9,2,2,2s2-0.9,2-2S10.1,10,9,10z M9,16c-1.1,0-2,0.9-2,2s0.9,2,2,2s2-0.9,2-2S10.1,16,9,16z"),
path(d := "M15,8c1.1,0,2-0.9,2-2s-0.9-2-2-2s-2,0.9-2,2S13.9,8,15,8z M15,10c-1.1,0-2,0.9-2,2s0.9,2,2,2s2-0.9,2-2S16.1,10,15,10z M15,16c-1.1,0-2,0.9-2,2s0.9,2,2,2s2-0.9,2-2S16.1,16,15,16z")
)
)
}
.build
def handleWithProps(props: Props): Unmounted[js.Object, Null] = SortableHandle(handleGrip)(props)
val handle: Unmounted[js.Object, Null] = handleWithProps(Props())
}
@dborisenko
Copy link
Author

Example of usage:

private val sortableItem: SortableElement.Props => ItemViewProps => Js.Unmounted[js.Object, Null] = SortableElement(itemView)
private val sortableList: SortableContainer.Props => ListViewProps => Js.Unmounted[js.Object, Null] = SortableContainer(listView)

// .render 
sortableItem(SortableElement.Props(index = index))(itemProps)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment