├── .gitignore
├── README.md
├── elm-package.json
├── example
├── .bootstraprc
├── README.md
├── elm-package.json
├── package.json
├── server
│ ├── index.js
│ ├── package.json
│ ├── test.html
│ └── uploads
│ │ └── .gitkeep
├── src
│ ├── Main.elm
│ ├── index.ejs
│ ├── index.js
│ └── styles.scss
└── webpack.config.js
└── src
├── FileReader.elm
├── FileReader
└── FileDrop.elm
└── Native
└── FileReader.js
/.gitignore:
--------------------------------------------------------------------------------
1 | elm-stuff
2 | node_modules
3 | ignore
4 | yarn.lock
5 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 0.19
2 |
3 | With the arrival of 0.19, the Javascript code in this package is unusable. I believe that some of the functionality in https://github.com/elm/file may provide an approved alternative, but I have not had cause to access files in 0.19 yet. So this repo is effectively archived pending the emergence of issues that can be solved in the current context.
4 |
5 | # Read files into Elm apps
6 |
7 | There are two basic ways to read files from the host operating system into the browser (either to view directly or to upload to a server):
8 |
9 | - HTML5 FileReader bindings
10 | - drag 'n drop into a target DOM element
11 |
12 | This helps with both methods and, in particular provides native bindings for the [HTML5 file reader control](http://www.w3.org/TR/html-markup/input.file.html) (the JS `FileReader` class).
13 |
14 | FileReader has three main methods (see [MDN](https://developer.mozilla.org/en/docs/Web/API/FileReader)):
15 |
16 | FileReaderInstance.readAsText();
17 | FileReaderInstance.readAsArrayBuffer();
18 | FileReaderInstance.readAsDataURL();
19 |
20 | The module also provides helper Elm json decoders for `change` events on `` and for relevant `drag` and `drop` events.
21 |
22 | ## Installation
23 |
24 | Due to the native (kernel) code, it is not possible to install directly using `elm-package install`. So you need on eof the following methods
25 |
26 | ### [elm-github-install](https://github.com/gdotdesign/elm-github-install)
27 |
28 | A tool to install native-code based Elm libraries
29 |
30 | Change you package.json file to readAsDataURL
31 |
32 | ```
33 | "dependencies": {
34 | "elm-lang/core": "5.0.0 <= v < 6.0.0",
35 | "elm-lang/html": "2.0.0 <= v < 3.0.0",
36 | [....],
37 | "simonh1000/file-reader": "1.6.0 <= v < 2.0.0"
38 | },
39 | ```
40 |
41 | ```
42 | (sudo) gem install elm_install
43 | elm-install
44 | ```
45 |
46 | ### Manually
47 |
48 | You can just copy the src code into your own source tree. Note in particular that you then need to add `"native-modules": true,` to your elm-package.json file as is done in the examples.
49 |
50 | ## Example
51 |
52 | The example provides a fully worked through file upload interface, taking advantage of most of the key functions in this library. If you want to try it, you must *first* edit src/Native/FileReader.js to swap the comments in the first two lines.
53 |
54 | ## Changelog
55 |
56 | 1.6: add drag and drop support
57 | 1.5: no new functionality
58 | 1.4: add `onFileChange` - an event handler for an ``
59 | 1.3.1: add rawBody
60 | 1.1: Update to 0.18. An additional native code function has been added to enable multipart form uploads of binary data - see http://simonh1000.github.io/2016/12/elm-s3-uploads/ for an example of its usage.
61 |
62 | ## Disclaimer
63 |
64 | This project began in the time of 0.16 and was submitted as a library including "native code" to the elm-package manager by [Daniel Bachler](https://github.com/danyx23) and myself. It was never OKed, as was the case with all native code at that time. The native code was subsequently updated by [WangBoxue](https://github.com/WangBoxue) to work with 0.17, and I ensured it worked for 0.18.
65 |
66 | In theory Evan plans to make all browser web APIs available to Elm users, and when that includes FileReader, this library will remove the native code. The official guidance therefore is to use a port rather than the native code in this library, but you can readily verify that the native code here covers the absolute minimum to expose the APIs, so I believe this will not jeopardise the stability of your Elm apps.
67 |
--------------------------------------------------------------------------------
/elm-package.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "1.6.1",
3 | "summary": "Elm bindings for HTML5 FileReader API",
4 | "repository": "https://github.com/simonh1000/file-reader.git",
5 | "license": "BSD3",
6 | "source-directories": [
7 | "src"
8 | ],
9 | "exposed-modules": [
10 | "FileReader",
11 | "FileReader.FileDrop"
12 | ],
13 | "native-modules": true,
14 | "dependencies": {
15 | "danyx23/elm-mimetype": "4.0.0 <= v < 5.0.0",
16 | "elm-lang/core": "5.0.0 <= v < 6.0.0",
17 | "elm-lang/html": "2.0.0 <= v < 3.0.0",
18 | "elm-lang/http": "1.0.0 <= v < 2.0.0"
19 | },
20 | "elm-version": "0.18.0 <= v < 0.19.0"
21 | }
22 |
--------------------------------------------------------------------------------
/example/.bootstraprc:
--------------------------------------------------------------------------------
1 | ---
2 | # Output debugging info
3 | # loglevel: debug
4 |
5 | # Major version of Bootstrap: 3 or 4
6 | bootstrapVersion: 4
7 |
8 | # If Bootstrap version 4 is used - turn on/off flexbox model
9 | # useFlexbox: true
10 | # preBootstrapCustomizations: ./src/precustomisation.scss
11 | # Webpack loaders, order matters
12 | styleLoaders:
13 | - style
14 | - css
15 | # - postcss
16 | - sass
17 |
18 | # Extract styles to stand-alone css file
19 | # Different settings for different environments can be used,
20 | # It depends on value of NODE_ENV environment variable
21 | # This param can also be set in webpack config:
22 | # entry: 'bootstrap-loader/extractStyles'
23 | extractStyles: false
24 | # env:
25 | # development:
26 | # extractStyles: false
27 | # production:
28 | # extractStyles: true
29 |
30 | # Customize Bootstrap variables that get imported before the original Bootstrap variables.
31 | # Thus original Bootstrap variables can depend on values from here. All the bootstrap
32 | # variables are configured with !default, and thus, if you define the variable here, then
33 | # that value is used, rather than the default. However, many bootstrap variables are derived
34 | # from other bootstrap variables, and thus, you want to set this up before we load the
35 | # official bootstrap versions.
36 | # For example, _variables.scss contains:
37 | # $input-color: $gray !default;
38 | # This means you can define $input-color before we load _variables.scss
39 | # preBootstrapCustomizations: ./app/styles/bootstrap/pre-customizations.scss
40 |
41 | # This gets loaded after bootstrap/variables is loaded and before bootstrap is loaded.
42 | # A good example of this is when you want to override a bootstrap variable to be based
43 | # on the default value of bootstrap. This is pretty specialized case. Thus, you normally
44 | # just override bootrap variables in preBootstrapCustomizations so that derived
45 | # variables will use your definition.
46 | #
47 | # For example, in _variables.scss:
48 | # $input-height: (($font-size-base * $line-height) + ($input-padding-y * 2) + ($border-width * 2)) !default;
49 | # This means that you could define this yourself in preBootstrapCustomizations. Or you can do
50 | # this in bootstrapCustomizations to make the input height 10% bigger than the default calculation.
51 | # Thus you can leverage the default calculations.
52 | # $input-height: $input-height * 1.10;
53 | # bootstrapCustomizations: ./app/styles/bootstrap/customizations.scss
54 |
55 | # Import your custom styles here. You have access to all the bootstrap variables. If you require
56 | # your sass files separately, you will not have access to the bootstrap variables, mixins, clases, etc.
57 | # Usually this endpoint-file contains list of @imports of your application styles.
58 | # appStyles: ./app/styles/app.scss
59 |
60 | ### Bootstrap styles
61 | styles:
62 |
63 | # Mixins
64 | mixins: true
65 |
66 | # Reset and dependencies
67 | # normalize: true
68 | print: true
69 |
70 | # Core CSS
71 | reboot: true
72 | type: true
73 | images: true
74 | code: true
75 | grid: true
76 | tables: true
77 | forms: true
78 | buttons: true
79 |
80 | # Components
81 | transitions: true
82 | dropdown: true
83 | button-group: true
84 | input-group: true
85 | custom-forms: true
86 | nav: true
87 | navbar: true
88 | card: true
89 | breadcrumb: true
90 | pagination: true
91 | jumbotron: true
92 | alert: true
93 | progress: true
94 | media: true
95 | list-group: true
96 | # responsive-embed: true
97 | close: true
98 | badge: true
99 |
100 | # Components w/ JavaScript
101 | # modal: true
102 | # tooltip: true
103 | # popover: true
104 | # carousel: true
105 |
106 | # Utility classes
107 | utilities: true
108 |
109 | ### Bootstrap scripts
110 | # scripts:
111 | # alert: true
112 | # button: true
113 | # carousel: true
114 | # collapse: true
115 | # dropdown: true
116 | # modal: true
117 | # popover: true
118 | # scrollspy: true
119 | # tab: true
120 | # tooltip: true
121 | # util: true
122 | #
123 |
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | # Elm File-Reader example
2 |
3 | 1. To use you need to edit /src/Native/FileReader.js and change the comments on the first two lines
4 | 2. Install and run using instructions below
5 | 3. Start server
6 | ```
7 | cd server
8 | npm install
9 | node index
10 | ```
11 | 4. Open http://localhost:3000 and try uploading a TEXT file
12 |
13 |
14 | ## Installation
15 |
16 | Based on https://github.com/simonh1000/elm-fullstack-starter
17 |
18 | With npm
19 |
20 | ```sh
21 | $ git clone git@github.com:simonh1000/elm-webpack-starter.git new-project
22 | $ cd new-project
23 | $ npm install
24 | $ npm run dev
25 | ```
26 |
27 | With yarn
28 | ```sh
29 | $ git clone git@github.com:simonh1000/elm-webpack-starter.git new-project
30 | $ cd new-project
31 | $ yarn
32 | $ yarn dev
33 | ```
34 |
--------------------------------------------------------------------------------
/example/elm-package.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "1.0.0",
3 | "summary": "elm-program",
4 | "repository": "https://github.com/user/project.git",
5 | "license": "ISC",
6 | "source-directories": [
7 | "src",
8 | "../src"
9 | ],
10 | "exposed-modules": [],
11 | "native-modules": true,
12 | "dependencies": {
13 | "danyx23/elm-mimetype": "4.0.0 <= v < 5.0.0",
14 | "elm-lang/core": "5.0.0 <= v < 6.0.0",
15 | "elm-lang/html": "2.0.0 <= v < 3.0.0",
16 | "elm-lang/http": "1.0.0 <= v < 2.0.0"
17 | },
18 | "elm-version": "0.18.0 <= v < 0.19.0"
19 | }
20 |
--------------------------------------------------------------------------------
/example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Simon Hampton",
3 | "name": "elm-webpack-starter",
4 | "version": "1.0.0",
5 | "description": "Elm starter with Webpack 3 hot-loading",
6 | "main": "index.js",
7 | "scripts": {
8 | "test": "elm-test",
9 | "start": "npm run dev",
10 | "dev": "webpack-dev-server --hot --port 3000",
11 | "build": "webpack",
12 | "prod": "webpack -p",
13 | "postinstall": "elm-package install -y && elm-test init"
14 | },
15 | "repository": {
16 | "type": "git",
17 | "url": "git+https://github.com/simonh1000/elm-webpack-starter.git"
18 | },
19 | "license": "MIT",
20 | "devDependencies": {
21 | "babel-core": "^6.25.0",
22 | "babel-loader": "^7.1.1",
23 | "babel-preset-env": "^1.6.0",
24 | "bootstrap-loader": "^2.2.0",
25 | "chokidar-cli": "^1.2.0",
26 | "clean-webpack-plugin": "^0.1.16",
27 | "copy-webpack-plugin": "^4.0.1",
28 | "css-loader": "^0.28.4",
29 | "elm-hot-loader": "^0.5.4",
30 | "elm-webpack-loader": "^4.3.1",
31 | "extract-text-webpack-plugin": "^3.0.0",
32 | "file-loader": "^0.11.2",
33 | "html-webpack-plugin": "^2.30.1",
34 | "node-sass": "^4.5.3",
35 | "postcss-loader": "^2.0.6",
36 | "resolve-url-loader": "^2.1.0",
37 | "sass-loader": "^6.0.6",
38 | "style-loader": "^0.18.2",
39 | "url-loader": "^0.5.9",
40 | "webpack": "^3.5.3",
41 | "webpack-dev-server": "^2.7.1",
42 | "webpack-merge": "^4.1.0"
43 | },
44 | "dependencies": {
45 | "bootstrap": "^4.0.0-beta",
46 | "loglevel": "^1.4.1"
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/example/server/index.js:
--------------------------------------------------------------------------------
1 | const fs = require('fs');
2 | const path = require('path');
3 |
4 | var express = require('express')
5 | var multer = require('multer')
6 | var upload = multer({ dest: 'uploads/' });
7 | var cors = require('cors');
8 |
9 | var app = express();
10 | app.use(cors());
11 |
12 | app.get('/', function (req, res) {
13 | // res.send('Hello World!')
14 | res.sendFile(__dirname+'/index.html');
15 | })
16 |
17 | app.get('/test', function (req, res) {
18 | res.sendFile(__dirname+'/test.html');
19 | })
20 |
21 | app.post('/upload', upload.single('upload'), function (req, res, next) {
22 | console.log(req.file);
23 | console.log(req.body);
24 | // fs.writeFileSync(req.body, path.join(__dirname, 'uploads', req.file.originalname));
25 | res.send({"message": req.file.filename});
26 | })
27 |
28 | app.listen(5000, function () {
29 | console.log('Example app listening on port 5000!')
30 | });
31 |
--------------------------------------------------------------------------------
/example/server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "multipartserver",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "",
10 | "license": "ISC",
11 | "dependencies": {
12 | "express": "^4.14.0",
13 | "multer": "^1.2.0"
14 | },
15 | "devDependencies": {
16 | "cors": "^2.8.1"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/example/server/test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
13 |
14 |
Test
15 |
16 |
17 |
18 |
19 |
68 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/example/server/uploads/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/simonh1000/file-reader/5291df8c67c05c6952574522ed7da07d583948d5/example/server/uploads/.gitkeep
--------------------------------------------------------------------------------
/example/src/Main.elm:
--------------------------------------------------------------------------------
1 | module Main exposing (main)
2 |
3 | import Html exposing (..)
4 | import Html.Attributes exposing (..)
5 | import Html.Events exposing (onClick)
6 | import Json.Decode as Json exposing (Value)
7 | import Task
8 | import Http
9 | import FileReader exposing (NativeFile)
10 | import FileReader.FileDrop as DZ
11 |
12 |
13 | type alias Model =
14 | { file : Maybe NativeFile
15 | , dragHovering : Int
16 | , content : String
17 | }
18 |
19 |
20 | init : Model
21 | init =
22 | { file = Nothing
23 | , dragHovering = 0
24 | , content = ""
25 | }
26 |
27 |
28 | type Msg
29 | = OnDragEnter Int
30 | | OnDrop (List NativeFile)
31 | | StartUpload
32 | | OnFileContent (Result FileReader.Error String)
33 | | PostResult (Result Http.Error Value)
34 | | NoOp
35 |
36 |
37 | update : Msg -> Model -> ( Model, Cmd Msg )
38 | update message model =
39 | case message of
40 | OnDragEnter inc ->
41 | ( { model | dragHovering = model.dragHovering + inc }, Cmd.none )
42 |
43 | OnDrop file ->
44 | case file of
45 | -- Only handling case of a single file
46 | [ f ] ->
47 | ( { model | file = Just f, dragHovering = 0 }, getFileContents f )
48 |
49 | _ ->
50 | ( { model | dragHovering = 0 }, Cmd.none )
51 |
52 | OnFileContent res ->
53 | case res of
54 | Ok content ->
55 | ( { model | content = content }, Cmd.none )
56 |
57 | Err err ->
58 | Debug.crash (toString err)
59 |
60 | StartUpload ->
61 | ( model, model.file |> Maybe.map sendFileToServer |> Maybe.withDefault Cmd.none )
62 |
63 | PostResult res ->
64 | case Debug.log "PostResult" res of
65 | _ ->
66 | ( model, Cmd.none )
67 |
68 | _ ->
69 | ( model, Cmd.none )
70 |
71 |
72 | view : Model -> Html Msg
73 | view model =
74 | let
75 | dzAttrs_ =
76 | DZ.dzAttrs (OnDragEnter 1) (OnDragEnter -1) NoOp OnDrop
77 |
78 | dzClass =
79 | if model.dragHovering > 0 then
80 | class "drop-zone active" :: dzAttrs_
81 | else
82 | class "drop-zone" :: dzAttrs_
83 | in
84 | div [ class "panel" ] <|
85 | [ h1 [] [ text "File Reader library example" ]
86 | , p [] [ text "Drag n Drop file below or use the file dialog to load file" ]
87 | , div dzClass
88 | [ input
89 | [ type_ "file"
90 | , FileReader.onFileChange OnDrop
91 | , multiple False
92 | ]
93 | []
94 | ]
95 | , case model.file of
96 | Just nf ->
97 | div []
98 | [ span [] [ text nf.name ]
99 | , button [ onClick StartUpload ] [ text "Upload" ]
100 | , div [] [ small [] [ text model.content ] ]
101 | ]
102 |
103 | Nothing ->
104 | text ""
105 | ]
106 |
107 |
108 |
109 | --
110 |
111 |
112 | getFileContents : NativeFile -> Cmd Msg
113 | getFileContents nf =
114 | FileReader.readAsTextFile nf.blob
115 | |> Task.attempt OnFileContent
116 |
117 |
118 | sendFileToServer : NativeFile -> Cmd Msg
119 | sendFileToServer nf =
120 | let
121 | body =
122 | Http.multipartBody
123 | [ Http.stringPart "part1" nf.name
124 | , FileReader.filePart "upload" nf
125 | ]
126 | in
127 | Http.post "http://localhost:5000/upload" body Json.value
128 | |> Http.send PostResult
129 |
130 |
131 |
132 | --
133 |
134 |
135 | main : Program Never Model Msg
136 | main =
137 | Html.program
138 | { init = ( init, Cmd.none )
139 | , update = update
140 | , view = view
141 | , subscriptions = always Sub.none
142 | }
143 |
--------------------------------------------------------------------------------
/example/src/index.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Elm hotloading dev environment
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/example/src/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | require('bootstrap-loader');
4 | require("./styles.scss");
5 |
6 | var Elm = require('./Main');
7 | var app = Elm.Main.fullscreen();
8 |
--------------------------------------------------------------------------------
/example/src/styles.scss:
--------------------------------------------------------------------------------
1 | body {
2 | background-color: #ddd;
3 | margin-top: 20px;
4 | display: flex;
5 | justify-content: center;
6 | align-items: center;
7 | .panel {
8 | background-color: whites;
9 | padding: 20px;
10 | width: 500px;
11 | }
12 | .drop-zone {
13 | border: 3px dashed #444;
14 | height: 300px;
15 | display: flex;
16 | justify-content: center;
17 | align-items: center;
18 | &.active {
19 | border-color: #dd9999;
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/example/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | const webpack = require('webpack');
3 | var merge = require('webpack-merge');
4 | var CopyWebpackPlugin = require('copy-webpack-plugin');
5 | var HTMLWebpackPlugin = require('html-webpack-plugin');
6 | const CleanWebpackPlugin = require('clean-webpack-plugin');
7 |
8 | var TARGET_ENV = process.env.npm_lifecycle_event === 'prod' ? 'production' : 'development';
9 | var filename = (TARGET_ENV == 'production') ? '[name]-[hash].js' : 'index.js';
10 |
11 | var common = {
12 | entry: './src/index.js',
13 | output: {
14 | path: path.join(__dirname, "dist"),
15 | // webpack -p automatically adds hash when building for production
16 | filename: filename
17 | },
18 | plugins: [new HTMLWebpackPlugin({
19 | // using .ejs prevents other loaders causing errors
20 | template: 'src/index.ejs',
21 | // inject details of output file at end of body
22 | inject: 'body'
23 | })],
24 | resolve: {
25 | modules: [
26 | path.join(__dirname, "src"),
27 | "node_modules"
28 | ],
29 | extensions: ['.js', '.elm', '.scss', '.png']
30 | },
31 | module: {
32 | rules: [
33 | {
34 | test: /\.html$/,
35 | exclude: /node_modules/,
36 | loader: 'file-loader?name=[name].[ext]'
37 | }, {
38 | test: /\.js$/,
39 | exclude: /node_modules/,
40 | use: {
41 | loader: 'babel-loader',
42 | options: {
43 | // env: automatically determines the Babel plugins you need based on your supported environments
44 | presets: ['env']
45 | }
46 | }
47 | }, {
48 | test: /\.scss$/,
49 | exclude: [
50 | /elm-stuff/, /node_modules/
51 | ],
52 | loaders: ["style-loader", "css-loader", "sass-loader"]
53 | }, {
54 | test: /\.css$/,
55 | exclude: [
56 | /elm-stuff/, /node_modules/
57 | ],
58 | loaders: ["style-loader", "css-loader"]
59 | }, {
60 | test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
61 | exclude: [
62 | /elm-stuff/, /node_modules/
63 | ],
64 | loader: "url-loader",
65 | options: {
66 | limit: 10000,
67 | mimetype: "application/font-woff"
68 | }
69 | }, {
70 | test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
71 | exclude: [
72 | /elm-stuff/, /node_modules/
73 | ],
74 | loader: "file-loader"
75 | }, {
76 | test: /\.(jpe?g|png|gif|svg)$/i,
77 | loader: 'file-loader'
78 | }
79 | ]
80 | }
81 | }
82 |
83 | if (TARGET_ENV === 'development') {
84 | console.log('Building for dev...');
85 | module.exports = merge(common, {
86 | plugins: [
87 | // Suggested for hot-loading
88 | new webpack.NamedModulesPlugin(),
89 | // Prevents compilation errors causing the hot loader to lose state
90 | new webpack.NoEmitOnErrorsPlugin()
91 | ],
92 | module: {
93 | rules: [
94 | {
95 | test: /\.elm$/,
96 | exclude: [
97 | /elm-stuff/, /node_modules/
98 | ],
99 | use: [
100 | {
101 | loader: "elm-hot-loader"
102 | }, {
103 | loader: "elm-webpack-loader",
104 | // add Elm's debug overlay to output
105 | options: {
106 | debug: true
107 | }
108 | }
109 | ]
110 | }
111 | ]
112 | },
113 | devServer: {
114 | inline: true,
115 | stats: 'errors-only',
116 | contentBase: path.join(__dirname, "src/assets"),
117 | // For SPAs: serve index.html in place of 404 responses
118 | historyApiFallback: true
119 | }
120 | });
121 | }
122 |
123 | if (TARGET_ENV === 'production') {
124 | console.log('Building for prod...');
125 | module.exports = merge(common, {
126 | plugins: [
127 | // Delete everything from output directory and report to user
128 | new CleanWebpackPlugin(['dist'], {
129 | root: __dirname,
130 | exclude: [],
131 | verbose: true,
132 | dry: false
133 | }),
134 | new CopyWebpackPlugin([
135 | {
136 | from: 'src/assets'
137 | }
138 | ]),
139 | // TODO update to version that handles =>
140 | new webpack.optimize.UglifyJsPlugin()
141 | ],
142 | module: {
143 | rules: [
144 | {
145 | test: /\.elm$/,
146 | exclude: [
147 | /elm-stuff/, /node_modules/
148 | ],
149 | use: [
150 | {
151 | loader: "elm-webpack-loader"
152 | }
153 | ]
154 | }
155 | ]
156 | }
157 | });
158 | }
159 |
--------------------------------------------------------------------------------
/src/FileReader.elm:
--------------------------------------------------------------------------------
1 | module FileReader
2 | exposing
3 | ( FileRef
4 | , FileContentArrayBuffer
5 | , FileContentDataUrl
6 | , NativeFile
7 | , Error(..)
8 | , onFileChange
9 | , readAsTextFile
10 | , readAsArrayBuffer
11 | , readAsDataUrl
12 | , prettyPrint
13 | , parseSelectedFiles
14 | , parseDroppedFiles
15 | , filePart
16 | , rawBody
17 | )
18 |
19 | {-| Elm bindings for the main [HTML5 FileReader APIs](https://developer.mozilla.org/en/docs/Web/API/FileReader):
20 |
21 | FileReaderInstance.readAsText();
22 | FileReaderInstance.readAsArrayBuffer();
23 | FileReaderInstance.readAsDataURL();
24 |
25 | The module also provides helper Json Decoders for the files values on
26 | `` `change` events, and on `drop` events,
27 | together with a set of examples.
28 |
29 |
30 | # API functions
31 |
32 | @docs readAsTextFile, readAsArrayBuffer, readAsDataUrl
33 |
34 |
35 | # Multi-part support
36 |
37 | @docs filePart, rawBody
38 |
39 |
40 | # Helper aliases
41 |
42 | @docs NativeFile, FileRef, FileContentArrayBuffer, FileContentDataUrl, Error, prettyPrint
43 |
44 |
45 | # Helper Json Decoders
46 |
47 | @docs parseSelectedFiles, parseDroppedFiles
48 |
49 |
50 | # Helpers: Html event handlers
51 |
52 | @docs onFileChange
53 |
54 | -}
55 |
56 | import Html exposing (Attribute)
57 | import Html.Events exposing (on)
58 | import Native.FileReader
59 | import Http exposing (Part, Body)
60 | import Task exposing (Task, fail)
61 | import Json.Decode as Json exposing (Decoder, Value)
62 | import MimeType
63 |
64 |
65 | {-| Helper type for interpreting the Files event value from Input and drag 'n drop.
66 | The first three elements are useful meta data, while the fourth is the handle
67 | needed to read the file.
68 |
69 | type alias NativeFile =
70 | { name : String
71 | , size : Int
72 | , mimeType : Maybe MimeType.MimeType
73 | , blob : Value
74 | }
75 |
76 | -}
77 | type alias NativeFile =
78 | { name : String
79 | , size : Int
80 | , mimeType : Maybe MimeType.MimeType
81 | , blob : FileRef
82 | }
83 |
84 |
85 | {-| A FileRef (or Blob) is a Elm Json Value.
86 | -}
87 | type alias FileRef =
88 | Value
89 |
90 |
91 | {-| An ArrayBuffer is a Elm Json Value.
92 | -}
93 | type alias FileContentArrayBuffer =
94 | Value
95 |
96 |
97 | {-| A DataUrl is an Elm Json Value.
98 | -}
99 | type alias FileContentDataUrl =
100 | Value
101 |
102 |
103 | {-| FileReader can fail in the following cases:
104 |
105 | - the File reference / blob passed in was not valid
106 | - an native error occurs during file reading
107 | - readAsTextFile is passed a FileRef that does not have a text format (unrecognised formats are read)
108 |
109 | -}
110 | type Error
111 | = NoValidBlob
112 | | ReadFail
113 | | NotTextFile
114 |
115 |
116 | {-| Takes a "File" or "Blob" JS object as a Json.Value. If the File is a text
117 | format, returns a task that reads the file as a text file. The Success value is
118 | represented as a String to Elm.
119 |
120 | readAsTextFile ref
121 |
122 | -}
123 | readAsTextFile : FileRef -> Task Error String
124 | readAsTextFile fileRef =
125 | if isTextFile fileRef then
126 | Native.FileReader.readAsTextFile fileRef
127 | else
128 | fail NotTextFile
129 |
130 |
131 | {-| Takes a "File" or "Blob" JS object as a Json.Value
132 | and starts a task to read the contents as an ArrayBuffer.
133 | The ArrayBuffer value returned in the Success case of the Task will
134 | be represented as a Json.Value to Elm.
135 |
136 | readAsArrayBuffer ref
137 |
138 | -}
139 | readAsArrayBuffer : FileRef -> Task Error FileContentArrayBuffer
140 | readAsArrayBuffer fileRef =
141 | Native.FileReader.readAsArrayBuffer fileRef
142 |
143 |
144 | {-| Takes a "File" or "Blob" JS object as a Json.Value
145 | and starts a task to read the contents as an DataURL (so it can
146 | be assigned to the src property of an img e.g.).
147 | The DataURL value returned in the Success case of the Task will
148 | be represented as a Json.Value to Elm.
149 |
150 | readAsDataUrl ref
151 |
152 | -}
153 | readAsDataUrl : FileRef -> Task Error FileContentDataUrl
154 | readAsDataUrl fileRef =
155 | Native.FileReader.readAsDataUrl fileRef
156 |
157 |
158 | {-| Creates an Http.Part from a NativeFile, to support uploading of binary files using multipart.
159 | -}
160 | filePart : String -> NativeFile -> Part
161 | filePart name nf =
162 | Native.FileReader.filePart name nf.blob
163 |
164 |
165 | {-| Creates an Http.Body from a NativeFile, to support uploading of binary files without using multipart.
166 | -}
167 | rawBody : String -> NativeFile -> Body
168 | rawBody mimeType nf =
169 | Native.FileReader.rawBody mimeType nf.blob
170 |
171 |
172 | {-| Pretty print FileReader errors.
173 | -}
174 | prettyPrint : Error -> String
175 | prettyPrint err =
176 | case err of
177 | ReadFail ->
178 | "File reading error"
179 |
180 | NoValidBlob ->
181 | "Blob was not valid"
182 |
183 | NotTextFile ->
184 | "Not a text file"
185 |
186 |
187 | {-| A 'change' event handler for a `input [ type_ "file" ] []` form element
188 | -}
189 | onFileChange : (List NativeFile -> msg) -> Attribute msg
190 | onFileChange msg =
191 | on "change" (Json.map msg parseSelectedFiles)
192 |
193 |
194 | {-| JSON Decoder for change event from an HTML input element with 'type="file"'.
195 | -}
196 | parseSelectedFiles : Decoder (List NativeFile)
197 | parseSelectedFiles =
198 | fileParser "target"
199 |
200 |
201 | {-| Parse files selected using an HTML drop event.
202 | Returns a list of files.
203 | -}
204 | parseDroppedFiles : Decoder (List NativeFile)
205 | parseDroppedFiles =
206 | fileParser "dataTransfer"
207 |
208 |
209 |
210 | -- UN-EXPORTED HELPERS
211 |
212 |
213 | {-| -- Used by readAsText, defaults to True if format not recognised
214 | -}
215 | isTextFile : FileRef -> Bool
216 | isTextFile fileRef =
217 | case Json.decodeValue mtypeDecoder fileRef of
218 | Ok (Just (MimeType.Text text)) ->
219 | True
220 |
221 | Ok Nothing ->
222 | True
223 |
224 | _ ->
225 | False
226 |
227 |
228 |
229 | {- DECODERS
230 | The Files event has a structure
231 |
232 | { 1 : file1..., 2: file2..., 3 : ... }
233 |
234 | It also inherits other properties that we need to ignore during parsing.
235 | fileParser achieves this by using Json.maybe and then filtering out Nothing(s)
236 | -}
237 |
238 |
239 | fileParser : String -> Decoder (List NativeFile)
240 | fileParser fieldName =
241 | Json.field fieldName <|
242 | Json.field "files" <|
243 | fileListDecoder nativeFileDecoder
244 |
245 |
246 | {-| Apply a decoder to each file in the FileList, in order.
247 | -}
248 | fileListDecoder : Decoder a -> Decoder (List a)
249 | fileListDecoder decoder =
250 | let
251 | decodeFileValues indexes =
252 | indexes
253 | |> List.map (\index -> Json.field (toString index) decoder)
254 | |> List.foldr (Json.map2 (::)) (Json.succeed [])
255 | in
256 | Json.field "length" Json.int
257 | |> Json.map (\i -> List.range 0 (i - 1))
258 | |> Json.andThen decodeFileValues
259 |
260 |
261 | {-| mime type: parsed as string and then converted to a MimeType
262 | -}
263 | mtypeDecoder : Decoder (Maybe MimeType.MimeType)
264 | mtypeDecoder =
265 | Json.map MimeType.parseMimeType (Json.field "type" Json.string)
266 |
267 |
268 | {-| blob: the whole JS File object as a Json.Value so we can pass
269 | it to a library that reads the content with a native FileReader
270 | -}
271 | nativeFileDecoder : Decoder NativeFile
272 | nativeFileDecoder =
273 | Json.map4 NativeFile
274 | (Json.field "name" Json.string)
275 | (Json.field "size" Json.int)
276 | mtypeDecoder
277 | Json.value
278 |
--------------------------------------------------------------------------------
/src/FileReader/FileDrop.elm:
--------------------------------------------------------------------------------
1 | module FileReader.FileDrop exposing (..)
2 |
3 | import Html exposing (Attribute)
4 | import Html.Events exposing (onWithOptions, Options)
5 | import Json.Decode as Json
6 | import FileReader exposing (parseDroppedFiles, NativeFile)
7 |
8 |
9 | dzAttrs : msg -> msg -> msg -> (List NativeFile -> msg) -> List (Attribute msg)
10 | dzAttrs dragEnter dragLeave dragOverMsg dropMsg =
11 | [ onDragEnter dragEnter
12 | , onDragLeave dragLeave
13 | , onDragOver dragOverMsg -- Needed for drop to work - should generally be passed NoOp
14 | , onDropFiles dropMsg
15 | ]
16 |
17 |
18 |
19 | --
20 |
21 |
22 | onDragEnter : msg -> Attribute msg
23 | onDragEnter msgCreator =
24 | onPreventDefault "dragenter" msgCreator
25 |
26 |
27 | onDragLeave : msg -> Attribute msg
28 | onDragLeave msgCreator =
29 | onPreventDefault "dragleave" msgCreator
30 |
31 |
32 | onDragOver : msg -> Attribute msg
33 | onDragOver =
34 | onPreventDefault "dragover"
35 |
36 |
37 |
38 | -- onDrop : msg -> Attribute msg
39 | -- onDrop msgCreator =
40 | -- onPreventDefault "drop" msgCreator
41 |
42 |
43 | onDropFiles : (List NativeFile -> msg) -> Attribute msg
44 | onDropFiles msgCreator =
45 | onWithOptions "drop" stopProp <|
46 | Json.map msgCreator parseDroppedFiles
47 |
48 |
49 |
50 | -- Helpers
51 |
52 |
53 | stopProp : Options
54 | stopProp =
55 | { stopPropagation = False, preventDefault = True }
56 |
57 |
58 | preventDef : Options
59 | preventDef =
60 | { stopPropagation = False, preventDefault = True }
61 |
62 |
63 | onStopPropagation : String -> a -> Attribute a
64 | onStopPropagation evt msgCreator =
65 | onWithOptions evt stopProp <|
66 | Json.succeed msgCreator
67 |
68 |
69 | onPreventDefault : String -> a -> Attribute a
70 | onPreventDefault evt msgCreator =
71 | onWithOptions evt preventDef <|
72 | Json.succeed msgCreator
73 |
--------------------------------------------------------------------------------
/src/Native/FileReader.js:
--------------------------------------------------------------------------------
1 | // To use the examples, swap the commenting on the next two lines
2 |
3 | // var _user$project$Native_FileReader = function() {
4 | var _simonh1000$file_reader$Native_FileReader = function() {
5 |
6 | var scheduler = _elm_lang$core$Native_Scheduler;
7 |
8 | function useReader(method, fileObjectToRead) {
9 | return scheduler.nativeBinding(function(callback){
10 |
11 | /*
12 | * Test for existence of FileReader using
13 | * if(window.FileReader) { ...
14 | * http://caniuse.com/#search=filereader
15 | * main gap is IE10 and 11 which do not support readAsBinaryFile
16 | * but we do not use this API either as it is deprecated
17 | */
18 | var reader = new FileReader();
19 |
20 | reader.onload = function(evt) {
21 | return callback(scheduler.succeed(evt.target.result));
22 | };
23 |
24 | reader.onerror = function() {
25 | return callback(scheduler.fail({ctor : 'ReadFail'}));
26 | };
27 |
28 | // Error if not passed an objectToRead or if it is not a Blob
29 | if (!fileObjectToRead || !(fileObjectToRead instanceof Blob)) {
30 | return callback(scheduler.fail({ctor : 'NoValidBlob'}));
31 | }
32 |
33 | if (reader[method]) {
34 | var result = reader[method](fileObjectToRead);
35 | // prevent memory leak by nullifying fileObjectToRead
36 | fileObjectToRead = null;
37 | return result;
38 | } else {
39 | return callback(scheduler.fail({ctor : 'ReadFail'}));
40 | }
41 | });
42 | }
43 |
44 | // readAsTextFile : Value -> Task error String
45 | var readAsTextFile = function(fileObjectToRead){
46 | return useReader("readAsText", fileObjectToRead);
47 | };
48 |
49 | // readAsArrayBuffer : Value -> Task error String
50 | var readAsArrayBuffer = function(fileObjectToRead){
51 | return useReader("readAsArrayBuffer", fileObjectToRead);
52 | };
53 |
54 | // readAsDataUrl : Value -> Task error String
55 | var readAsDataUrl = function(fileObjectToRead){
56 | return useReader("readAsDataURL", fileObjectToRead);
57 | };
58 |
59 | var filePart = function(name, blob) {
60 | return {
61 | _0: name,
62 | _1: blob
63 | }
64 | };
65 |
66 | var rawBody = function (mimeType, blob) {
67 | return {
68 | ctor: "StringBody",
69 | _0: mimeType,
70 | _1: blob
71 | };
72 | };
73 |
74 | return {
75 | readAsTextFile : readAsTextFile,
76 | readAsArrayBuffer : readAsArrayBuffer,
77 | readAsDataUrl: readAsDataUrl,
78 | filePart: F2(filePart),
79 | rawBody: F2(rawBody)
80 | };
81 | }();
82 |
--------------------------------------------------------------------------------