Skip to content

Commit

Permalink
Fix/image crop (#818)
Browse files Browse the repository at this point in the history
* Fix image height

* Show error in console

* Fix issues on android

* Fix for safari

* Fix according to standardjs

* Fix according to elm-review

* Fix Dragged handler
  • Loading branch information
henriquecbuss authored Aug 24, 2022
1 parent 565546d commit 24f43e6
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 96 deletions.
1 change: 1 addition & 0 deletions src/customElements/imageCropper.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export default () => (
}

if (Object.values(allInputValues).some(x => x !== 0 && !x)) {
console.error('ImageCropper: missing input values', { cause: Object.keys(allInputValues).filter(x => !allInputValues[x]) })
throw new Error('ImageCropper: missing input values', { cause: Object.keys(allInputValues).filter(x => !allInputValues[x]) })
}

Expand Down
53 changes: 42 additions & 11 deletions src/customElements/pointerListener.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,25 @@ export default () => (
this.previousX = null
this.previousY = null

this.listeners = {
'pointermove': (e) => {
if (this.previousX == null || this.previousY == null) {
this.dragImageElement = document.createElement('span')
this.dragImageElement.classList.add('sr-only')
this.dragImageElement.classList.add('pointer-events-none')
this.dragImageElement.setAttribute('aria-hidden', 'true')
document.body.appendChild(this.dragImageElement)

this.documentListeners = {
'dragover': (e) => {
if (this.previousX === null || this.previousY === null) {
this.previousX = e.clientX
this.previousY = e.clientY
return
}

this.dispatchEvent(new CustomEvent('document-pointermove', {
if (this.previousX === e.clientX && this.previousY === e.clientY) {
return
}

this.dispatchEvent(new CustomEvent('document-dragover', {
detail: {
clientX: e.clientX,
clientY: e.clientY,
Expand All @@ -25,22 +35,43 @@ export default () => (

this.previousX = e.clientX
this.previousY = e.clientY
}
}

this.elementListeners = {
'dragstart': (e) => {
this.dispatchEvent(new CustomEvent('element-dragstart'))

e.dataTransfer.setDragImage(this.dragImageElement, 0, 0)
},
'pointerup': () => {
this.dispatchEvent(new CustomEvent('document-pointerup'))
'dragend': () => {
this.dispatchEvent(new CustomEvent('element-dragend'))
}
}

Object.keys(this.listeners).forEach((key) => {
document.addEventListener(key, this.listeners[key])
Object.keys(this.documentListeners).forEach((key) => {
document.addEventListener(key, this.documentListeners[key])
})

Object.keys(this.elementListeners).forEach((key) => {
this.addEventListener(key, this.elementListeners[key])
})
}

disconnectedCallback () {
Object.keys(this.listeners).forEach((key) => {
document.removeEventListener(key, this.listeners[key])
if (this.dragImageElement && document.body.contains(this.dragImageElement)) {
document.body.removeChild(this.dragImageElement)
}

Object.keys(this.elementListeners).forEach((key) => {
this.removeEventListener(key, this.elementListeners[key])
})
this.listeners = undefined

Object.keys(this.documentListeners).forEach((key) => {
document.removeEventListener(key, this.documentListeners[key])
})

this.documentListeners = undefined
}
}
)
118 changes: 76 additions & 42 deletions src/elm/View/Components.elm
Original file line number Diff line number Diff line change
Expand Up @@ -598,54 +598,68 @@ intersectionObserver options =
[]


{-| A component that attaches events related to pointers to the document and
passes it into Elm
{-| A component that attaches events related to pointers to the document and the
element and passes it into Elm.
- `document.onDragOver`: This event is fired when something is being dragged and
the pointer moves.
- `element.onDragEnd`: This event is fired when the user releases the pointer
after a drag operation.
- `element.onDragStart`: This event is fired whenever the user starts dragging
an element.
Make sure the element has `draggble = true`.
-}
pointerListener :
{ onPointerMove :
Maybe
({ x : Float
, y : Float
, previousX : Float
, previousY : Float
}
-> msg
)
, onPointerUp : Maybe msg
{ document :
{ onDragOver :
Maybe
({ x : Float
, y : Float
, previousX : Float
, previousY : Float
}
-> msg
)
}
, element :
{ onDragStart :
Maybe
{ dragImage : Maybe String
, listener : msg
}
, onDragEnd : Maybe msg
}
}
-> List (Html.Attribute msg)
-> List (Html msg)
-> Html msg
pointerListener { onPointerMove, onPointerUp } =
pointerListener { document, element } attrs children =
node "pointer-listener"
[ case onPointerMove of
Nothing ->
class ""

Just pointerMoveListener ->
on "document-pointermove"
(Json.Decode.field "detail"
(Json.Decode.map4
(\x y previousX previousY ->
pointerMoveListener
{ x = x
, y = y
, previousX = previousX
, previousY = previousY
}
)
(Json.Decode.field "clientX" Json.Decode.float)
(Json.Decode.field "clientY" Json.Decode.float)
(Json.Decode.field "previousX" Json.Decode.float)
(Json.Decode.field "previousY" Json.Decode.float)
)
(optionalListenerWithArgs "document-dragover"
document.onDragOver
(Json.Decode.field "detail"
(Json.Decode.map4
(\x y previousX previousY ->
{ x = x
, y = y
, previousX = previousX
, previousY = previousY
}
)
, case onPointerUp of
Nothing ->
class ""

Just pointerUpListener ->
on "document-pointerup" (Json.Decode.succeed pointerUpListener)
]
[]
(Json.Decode.field "clientX" Json.Decode.float)
(Json.Decode.field "clientY" Json.Decode.float)
(Json.Decode.field "previousX" Json.Decode.float)
(Json.Decode.field "previousY" Json.Decode.float)
)
)
:: optionalListener "element-dragend" element.onDragEnd
:: optionalListener "element-dragstart" (Maybe.map .listener element.onDragStart)
:: optionalAttr "element-pointerdown-drag-image" (Maybe.andThen .dragImage element.onDragStart)
:: attrs
)
children



Expand Down Expand Up @@ -687,6 +701,26 @@ optionalAttr attr maybeAttr =
attribute attr attrValue


optionalListener : String -> Maybe msg -> Html.Attribute msg
optionalListener eventName maybeMsg =
case maybeMsg of
Nothing ->
class ""

Just msg ->
on eventName (Json.Decode.succeed msg)


optionalListenerWithArgs : String -> Maybe (a -> msg) -> Json.Decode.Decoder a -> Html.Attribute msg
optionalListenerWithArgs eventName maybeMsg decoder =
case maybeMsg of
Nothing ->
class ""

Just msg ->
on eventName (Json.Decode.map msg decoder)


dateFormatter :
List (Html.Attribute msg)
-> { language : Translation.Language, date : Time.Posix, translationString : String }
Expand Down
82 changes: 39 additions & 43 deletions src/elm/View/ImageCropper.elm
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import Browser.Dom
import Dict
import File
import Html exposing (Html, button, div, img, input, node)
import Html.Attributes exposing (alt, attribute, class, classList, id, src, style, type_, value)
import Html.Attributes exposing (alt, attribute, class, classList, draggable, id, src, style, type_, value)
import Html.Events
import Icons
import Json.Decode
Expand Down Expand Up @@ -229,31 +229,29 @@ update msg model =
]
}

Dragged { x, y, previousX, previousY } ->
Dragged { x, y } ->
case model.dimmensions of
Loading ->
UR.init model

Loaded dimmensions ->
let
selection =
calculateSelectionDimmensions model dimmensions
in
{ model
| dimmensions =
Loaded
{ dimmensions
| topOffset =
clamp dimmensions.container.top
(dimmensions.container.top + dimmensions.container.height - selection.height)
(dimmensions.topOffset + y - previousY)
, leftOffset =
clamp dimmensions.container.left
(dimmensions.container.left + dimmensions.container.width - selection.width)
(dimmensions.leftOffset + x - previousX)
}
}
|> UR.init
if not model.isDragging then
UR.init model

else
let
selection =
calculateSelectionDimmensions model dimmensions
in
{ model
| dimmensions =
Loaded
{ dimmensions
| topOffset = y - selection.height / 2
, leftOffset = x - selection.width / 2
}
}
|> UR.init

ChangedDimmensions percentageString ->
case model.dimmensions of
Expand Down Expand Up @@ -329,7 +327,7 @@ view model { imageUrl, cropperAttributes } =
[ src imageUrl
, alt ""
, id entireImageId
, class "opacity-20 pointer-events-none select-none max-h-full"
, class "opacity-20 pointer-events-none select-none max-w-full max-h-[35vh] lg:max-h-[60vh]"
, Html.Events.on "load" (Json.Decode.succeed ImageLoaded)
]
[]
Expand Down Expand Up @@ -377,14 +375,31 @@ viewCropper model dimmensions { imageUrl, cropperAttributes } =
floatToPx offset =
String.fromFloat offset ++ "px"
in
[ div
[ View.Components.pointerListener
{ document =
{ onDragOver =
if model.isDragging then
Just Dragged

else
Nothing
}
, element =
{ onDragStart =
Just
{ dragImage = Nothing
, listener = StartedDragging
}
, onDragEnd = Just StoppedDragging
}
}
(class "absolute overflow-hidden border border-dashed border-gray-400 cursor-move z-20 select-none mx-auto"
:: classList [ ( "transition-all origin-center", not model.isDragging && not model.isChangingDimmensions && not model.isReflowing ) ]
:: style "top" (floatToPx topOffset)
:: style "left" (floatToPx leftOffset)
:: style "width" (floatToPx selection.width)
:: style "height" (floatToPx selection.height)
:: onPointerDown StartedDragging
:: draggable "true"
:: List.map (Html.Attributes.map Basics.never) cropperAttributes
)
[ img
Expand All @@ -401,14 +416,6 @@ viewCropper model dimmensions { imageUrl, cropperAttributes } =
]
[]
]
, if model.isDragging then
View.Components.pointerListener
{ onPointerMove = Just Dragged
, onPointerUp = Just StoppedDragging
}

else
Html.text ""
, node "image-cropper"
[ if model.isRequestingCroppedImage then
attribute "elm-generate-new-cropped-image" "true"
Expand Down Expand Up @@ -500,17 +507,6 @@ calculateSelectionDimmensions model dimmensions =
}


onPointerDown : msg -> Html.Attribute msg
onPointerDown msg =
Html.Events.custom "pointerdown"
(Json.Decode.succeed
{ message = msg
, stopPropagation = True
, preventDefault = True
}
)


msgToString : Msg -> List String
msgToString msg =
case msg of
Expand Down

0 comments on commit 24f43e6

Please sign in to comment.