-- inspired from here: https://ellie-app.com/3T5mNms7SwKa1 module Comp.Dropzone exposing ( Model , Msg(..) , Settings , defaultSettings , init , setActive , update , view ) import File exposing (File) import File.Select import Html exposing (..) import Html.Attributes exposing (..) import Html.Events exposing (..) import Json.Decode as D type alias State = { hover : Bool , active : Bool } type alias Settings = { classList : State -> List ( String, Bool ) , contentTypes : List String } defaultSettings : Settings defaultSettings = { classList = \_ -> [ ( "ui placeholder segment", True ) ] , contentTypes = [ "application/pdf" ] } type alias Model = { state : State , settings : Settings } init : Settings -> Model init settings = { state = State False True , settings = settings } type Msg = DragEnter | DragLeave | GotFiles File (List File) | PickFiles | SetActive Bool setActive : Bool -> Msg setActive flag = SetActive flag update : Msg -> Model -> ( Model, Cmd Msg, List File ) update msg model = case msg of SetActive flag -> let ns = { hover = model.state.hover, active = flag } in ( { model | state = ns }, Cmd.none, [] ) PickFiles -> ( model, File.Select.files model.settings.contentTypes GotFiles, [] ) DragEnter -> let ns = { hover = True, active = model.state.active } in ( { model | state = ns }, Cmd.none, [] ) DragLeave -> let ns = { hover = False, active = model.state.active } in ( { model | state = ns }, Cmd.none, [] ) GotFiles file files -> let ns = { hover = False, active = model.state.active } newFiles = if model.state.active then filterMime model.settings (file :: files) else [] in ( { model | state = ns }, Cmd.none, newFiles ) view : Model -> Html Msg view model = div [ classList (model.settings.classList model.state) , hijackOn "dragenter" (D.succeed DragEnter) , hijackOn "dragover" (D.succeed DragEnter) , hijackOn "dragleave" (D.succeed DragLeave) , hijackOn "drop" dropDecoder ] [ div [ class "ui icon header" ] [ i [ class "mouse pointer icon" ] [] , div [ class "content" ] [ text "Drop files here" , div [ class "sub header" ] [ text "PDF files only" ] ] ] , div [ class "ui horizontal divider" ] [ text "Or" ] , a [ classList [ ( "ui basic primary button", True ) , ( "disabled", not model.state.active ) ] , onClick PickFiles , href "" ] [ i [ class "folder open icon" ] [] , text "Select ..." ] ] filterMime : Settings -> List File -> List File filterMime settings files = let pred f = List.member (File.mime f) settings.contentTypes in List.filter pred files dropDecoder : D.Decoder Msg dropDecoder = D.at [ "dataTransfer", "files" ] (D.oneOrMore GotFiles File.decoder) hijackOn : String -> D.Decoder msg -> Attribute msg hijackOn event decoder = preventDefaultOn event (D.map hijack decoder) hijack : msg -> ( msg, Bool ) hijack msg = ( msg, True )