Skip to content

Instantly share code, notes, and snippets.

@MangelMaxime
Created June 27, 2019 13:51
Show Gist options
  • Save MangelMaxime/973e4bf2f1db9e04d1b58a18b1c035cd to your computer and use it in GitHub Desktop.
Save MangelMaxime/973e4bf2f1db9e04d1b58a18b1c035cd to your computer and use it in GitHub Desktop.
Drag & Drop using Elmish and React hooks
html,
body {
font-size: 16px;
}
module DragAndDrop
open Fable.Core
open Fable.Core.JsInterop
open Fable.React
open Fable.React.Props
open Browser
open Elmish
open Elmish.React
type Coords =
{
X : float
Y : float
}
type Circle =
{
X : float
Y : float
Coords : Coords
IsDragging : bool
}
type Model = Circle list
type Msg =
| StartDragging of index : int * pageX : float * pageY : float
| Dragging of index : int * pageX : float * pageY : float
| EndDragging of index : int
let init () =
[
{
X = 50.
Y = 50.
Coords =
{
X = 0.
Y = 0.
}
IsDragging = false
}
{
X = 150.
Y = 150.
Coords =
{
X = 0.
Y = 0.
}
IsDragging = false
}
]
, Cmd.none
let update (msg : Msg) (currentModel : Model) =
match msg with
| StartDragging (rank, pageX, pageY) ->
currentModel
|> List.mapi (fun index circle ->
if rank <> index then
circle
else
{ circle with
Coords =
{
X = pageX
Y = pageY
}
IsDragging = true
}
)
, Cmd.none
| Dragging (rank, pageX, pageY) ->
currentModel
|> List.mapi (fun index circle ->
if rank <> index then
circle
else
let xDiff = circle.Coords.X - pageX
let yDiff = circle.Coords.Y - pageY
{ circle with
X = circle.X - xDiff
Y = circle.Y - yDiff
Coords =
{
X = pageX
Y = pageY
}
}
)
, Cmd.none
| EndDragging rank ->
currentModel
|> List.mapi (fun index circle ->
if rank <> index then
circle
else
{ circle with
IsDragging = false
}
)
, Cmd.none
type RenderCircleProps =
{
Circle : Circle
Index : int
Dispatch : Dispatch<Msg>
key : string
}
let renderCircle =
FunctionComponent.Of(
fun (props : RenderCircleProps) ->
let handleMouseMove =
Hooks.useRef(fun (ev : Types.Event) ->
let ev = ev :?> Types.MouseEvent
Dragging (props.Index, ev.pageX, ev.pageY)
|> props.Dispatch
)
let color =
if props.Circle.IsDragging then
"lightblue"
else
"grey"
circle
[
OnMouseUp (fun ev ->
document.removeEventListener("mousemove", handleMouseMove.current)
EndDragging props.Index
|> props.Dispatch
)
OnMouseDown (fun ev ->
StartDragging (props.Index, ev.pageX, ev.pageY)
|> props.Dispatch
document.addEventListener("mousemove", handleMouseMove.current)
)
Cx props.Circle.X
Cy props.Circle.Y
R 25.
SVGAttr.Fill color
SVGAttr.Stroke color
SVGAttr.StrokeWidth 1
]
[ ]
, "Circle"
, equalsButFunctions
)
let view (model : Model) (dispatch : Msg -> unit) =
let circles =
model
|> List.mapi (fun index circle ->
renderCircle
{
Circle = circle
Index = index
Dispatch = dispatch
key = "circle-" + string index
}
)
|> ofList
svg [
Style
[
Border "1px solid green"
Height "500px"
Width "calc(100% - 20px)"
Margin "10px"
]
]
[ circles ]
// App
Program.mkProgram init update view
|> Program.withReactSynchronous "elmish-app"
|> Program.run
<html>
<head>
<meta http-equiv='Content-Type' content='text/html; charset=utf-8'>
<script src="__HOST__/libs/react.production.min.js"></script>
<script src="__HOST__/libs/react-dom.production.min.js"></script>
<link rel="stylesheet" href="__HOST__/libs/css/bulma.min.css" />
<link rel="stylesheet" href="__HOST__/libs/css/all.min.css" />
</head>
<body class="app-container">
<div id="elmish-app" class="elmish-app"></div>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment