@@ -1,27 +1,35 @@
1module Yoga.Grimoire.Component where
1module Yoga.Grimoire.Component where
3import Prelude
3import Prelude
4−import Data.Array (mapWithIndex, zip)
4+import Data.Array (mapWithIndex, zip, (!!))
5import Data.Array as Array
5import Data.Array as Array
6−import Data.Maybe (Maybe(..))
6+import Data.FoldableWithIndex (findWithIndex)
8+import Data.Lens.Index (ix)
9+import Data.Maybe (Maybe(..), fromMaybe, isJust)
7import Data.Nullable (Nullable)
10import Data.Nullable (Nullable)
11+import Data.Nullable as Nullable
12+import Data.Traversable (for, sequence)
8−import Data.Tuple.Nested ((/\))
13+import Data.Tuple.Nested ((/\), type (/\))
9import Debug.Trace (spy)
14import Debug.Trace (spy)
10import Effect (Effect)
15import Effect (Effect)
11import Justifill (justifill)
16import Justifill (justifill)
12import React.Basic (ReactComponent)
17import React.Basic (ReactComponent)
13import React.Basic.DOM (css)
18import React.Basic.DOM (css)
14import React.Basic.Helpers (jsx)
19import React.Basic.Helpers (jsx)
15−import React.Basic.Hooks (Ref, component, element)
20+import React.Basic.Hooks (Ref, component, element, readRef, useLayoutEffect, useRef, writeRef)
16import React.Basic.Hooks as React
21import React.Basic.Hooks as React
17import React.Basic.Hooks.Spring (animatedDiv, useSprings)
22import React.Basic.Hooks.Spring (animatedDiv, useSprings)
18import React.Basic.Hooks.UseGesture (useDrag, withDragProps)
23import React.Basic.Hooks.UseGesture (useDrag, withDragProps)
19import Record.Extra (pick)
24import Record.Extra (pick)
20import Unsafe.Coerce (unsafeCoerce)
25import Unsafe.Coerce (unsafeCoerce)
21import Web.DOM (Node)
26import Web.DOM (Node)
27+import Web.HTML.HTMLElement (DOMRect, getBoundingClientRect)
28+import Web.HTML.HTMLElement as HTMLElement
22import Yoga.Grid.Component as Grid
29import Yoga.Grid.Component as Grid
23import Yoga.Grimoire.Spell.Component as GrimoireSpell
30import Yoga.Grimoire.Spell.Component as GrimoireSpell
24import Yoga.Grimoire.Styles (styles)
31import Yoga.Grimoire.Styles (styles)
32+import Yoga.Helpers ((?||))
25import Yoga.Spell.Types (Spell)
33import Yoga.Spell.Types (Spell)
26import Yoga.Theme.Styles (makeStylesJSS, useTheme)
34import Yoga.Theme.Styles (makeStylesJSS, useTheme)
27import Yoga.Theme.Types (CSSTheme)
35import Yoga.Theme.Types (CSSTheme)
@@ -30,6 +38,40 @@ type Props
30 = { spells ∷ Array Spell
38 = { spells ∷ Array Spell
42+ refNodes <- readRef ref
44+ refNodes \nullinger ->
46+ Nullable.toMaybe nullinger >>= HTMLElement.fromNode <#> getBoundingClientRect
48+center ∷ DOMRect -> (Number /\ Number)
49+center { left, top, width, height } = (left + width / 2.0) /\ (top + height / 2.0)
51+overlaps ∷ DOMRect -> DOMRect -> Boolean
52+overlaps r1 { left, top, right, bottom } = between left right x && between top bottom y
56+emptyDomRect ∷ DOMRect
57+emptyDomRect = { top: 0.0, right: 0.0, bottom: 0.0, left: 0.0, width: 0.0, height: 0.0 }
59+translate ∷ (Number /\ Number) -> DOMRect -> DOMRect
60+translate (x /\ y) { top, right, bottom, left, width, height } =
69+swap ∷ ∀ a. Int -> Int -> Array a -> Maybe (Array a)
73+ pure $ (set (ix i) valueJ <<< set (ix j) valueI) arr
33makeComponent ∷ Effect (ReactComponent Props)
75makeComponent ∷ Effect (ReactComponent Props)
34makeComponent = do
76makeComponent = do
35 grid <- Grid.makeComponent
77 grid <- Grid.makeComponent
@@ -38,21 +80,56 @@ makeComponent = do
38 component "Grimoire" \(props ∷ Props) -> React.do
80 component "Grimoire" \(props ∷ Props) -> React.do
39 {} <- useStyles (pick props)
81 {} <- useStyles (pick props)
40 springs <- useSprings (Array.length props.spells) \_ -> { x: 0.0, y: 0.0, zIndex: 0, immediate: false, transform: "scale3d(1.0,1.0,1.0)" }
82 springs <- useSprings (Array.length props.spells) \_ -> { x: 0.0, y: 0.0, zIndex: 0, immediate: false, transform: "scale3d(1.0,1.0,1.0)" }
41− let
83+ nodeRefs <- useRef (props.spells $> (Nullable.null ∷ (_ Node)))
42− _ = spy "springs" springs
84+ positionsRef <- useRef ([] ∷ _ DOMRect)
85+ originalPositionsRef <- useRef ([] ∷ _ DOMRect)
86+ useLayoutEffect unit do
87+ rects <- getRects nodeRefs
88+ writeRef positionsRef (rects <#> fromMaybe emptyDomRect)
89+ writeRef originalPositionsRef (rects <#> fromMaybe emptyDomRect)
43 theme ∷ CSSTheme <- useTheme
91 theme ∷ CSSTheme <- useTheme
44 bindDragProps <-
92 bindDragProps <-
45− useDrag (justifill {}) \{ arg, down, movement: mx /\ my } -> do
93+ useDrag (justifill {}) \{ arg, down, movement: mx /\ my, xy: x /\ y } -> do
94+ rects <- getRects nodeRefs
95+ originalPositions <- readRef originalPositionsRef
96+ positionsBefore <- readRef positionsRef
98+ originalPosDraggedBefore = positionsBefore !! arg ?|| emptyDomRect
100+ currentPosDraggedBefore = originalPosDraggedBefore # translate (mx /\ my)
102+ overlapsOtherBefore = positionsBefore # findWithIndex \i' pos -> i' /= arg && overlaps currentPosDraggedBefore pos
103+ case down, overlapsOtherBefore of
104+ false, Just { index, value } -> writeRef positionsRef (swap arg index positionsBefore ?|| positionsBefore)
106+ positions <- readRef positionsRef
108+ originalPosDragged = positions !! arg ?|| emptyDomRect
110+ currentPosDragged = originalPosDragged # translate (mx /\ my)
112+ overlapsOther = positions # findWithIndex \i' pos -> i' /= arg && overlaps currentPosDragged pos
46− springs.set \i ->
113+ springs.set \i -> do
47− if i == arg && down then
114+ let
115+ iPos = positions !! i ?|| emptyDomRect
117+ origIPos = originalPositions !! i ?|| emptyDomRect
119+ leftOffset = if iPos == origIPos then 0.0 else iPos.left - origIPos.left
121+ topOffset = if iPos == origIPos then 0.0 else iPos.top - origIPos.top
122+ case i == arg, down, overlapsOther of
123+ false, true, Just { index, value }
124+ | index == i -> { x: originalPosDragged.left - value.left + leftOffset, y: originalPosDragged.top - value.top + topOffset, zIndex: 0, transform: "scale3d(1.0, 1.0, 1.0)", immediate: const false }
48− { x: mx, y: my, zIndex: 1, transform: "scale3d(1.1, 1.1, 1.1)", immediate: \n -> n == "x" || n == "y" || n == "zIndex" }
125+ true, true, _ -> { x: mx + leftOffset, y: my + topOffset, zIndex: 1, transform: "scale3d(1.1, 1.1, 1.1)", immediate: \n -> n == "x" || n == "y" || n == "zIndex" }
50− { x: 0.0, y: 0.0, zIndex: 0, transform: "scale3d(1.0, 1.0, 1.0)", immediate: const false }
126+ _, _, _ -> { x: leftOffset, y: topOffset, zIndex: 0, transform: "scale3d(1.0, 1.0, 1.0)", immediate: const false }
52 renderSpells =
128 renderSpells =
53 mapWithIndex \i (spell /\ style) ->
129 mapWithIndex \i (spell /\ style) ->
54 animatedDiv
130 animatedDiv
55 $ { style: css style
131 $ { style: css style
132+ , ref: unsafeCoerce (unsafeUpdateRefs nodeRefs i)
56 , children:
133 , children:
57 [ element
134 [ element
58 spellComponent
135 spellComponent