summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJohn Omar <me@johnomar.com>2016-10-13 11:31:25 -0400
committerJohn Omar <me@johnomar.com>2016-10-13 11:31:25 -0400
commit0c190e062e77b9c5012b7a693fa48861b68bcb29 (patch)
treee57780251f711dd6578f46422f3c7563047d5fcb
webpack working.
-rw-r--r--.gitignore7
-rw-r--r--README.md330
-rw-r--r--elm-package.json15
-rw-r--r--main.js39
-rw-r--r--package.json19
-rw-r--r--src/elm/Main.elm7
-rw-r--r--src/static/bundle.js7446
-rw-r--r--src/static/index.html10
-rw-r--r--src/static/index.js3
-rw-r--r--webpack.config.js19
10 files changed, 7895 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..040dcb5
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,7 @@
+node_modules
+# elm-package generated files
+elm-stuff
+# elm-repl generated files
+repl-temp-*
+
+dist \ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..2e72c44
--- /dev/null
+++ b/README.md
@@ -0,0 +1,330 @@
+* Electron
+* Elm
+* webpack
+* hot reloading
+
+# A guide, not a template
+You can find starter templates for a lot of frameworks in the javascript ecosystem. They aren't helpful.
+Templates make your life harder. When something goes wrong or your code veers slightly from the template,
+you'll be fighting a beast that you likely do not understand. More so, the template probably includes
+packages and settings that you don't need.
+
+So here it goes.
+
+# Create some folders and files
+Whenever you see *Project-Name* you should substitute it with the name of your project. Per traditon,
+let's put your project files in a folder called Project-Name: `mkdir Project-Name`
+
+Now let's make a few files and folders.
+
+```
+cd Project-Name
+touch README.md webpack.config.js
+mkdir src src/elm src/static
+npm init
+```
+
+After `npm init` you'll be presented with a few questions about the project. Answer them or keep pressing enter. Now
+is a good time to initialize git and make your first commit.
+
+Note that the `/elm` and `/static` folders inside `/src` are how I choose to structure my elm projects. Elm files have their
+place, and js/html/css go into static.
+
+
+# Electron
+Electron allows you to use web technologies to build desktop apps. As of now, Elm compiles to javascript. That's why
+we can use Elm to build desktop apps. The goal in this section is to get an electron window displaying "Hello Electron". We won't
+be using Elm yet.
+
+First make sure electron is globally installed.
+
+```
+npm install -g electron-prebuilt
+```
+
+Electron has a Main process and a Renderer process. You can think of the Main process as the code that interacts with the
+file system and desktop with node.js. It's a local server. The Renderer process is where you write the front end code that
+the user interacts with. You can send data between processes using [ipc](http://electron.atom.io/docs/api/ipc-main/).
+
+Let's create two files to see this in action.
+
+```
+touch main.js src/static/index.html
+```
+
+Fill in the index.html file with text that shows you that this is working.
+
+```
+<html>
+
+ <head>
+ <title>This title shows at the top</title>
+ </head>
+
+ <body>
+ <h2>Hello Electron</h2>
+ </body>
+</html>
+```
+
+And now the main.js file.
+
+```
+'use strict'
+const electron = require('electron')
+
+const app = electron.app // this is our app
+const BrowserWindow = electron.BrowserWindow // This is a Module that creates windows
+
+let mainWindow // saves a global reference to mainWindow so it doesn't get garbage collected
+
+app.on('ready', createWindow) // called when electron has initialized
+
+// This will create our app window, no surprise there
+function createWindow () {
+ mainWindow = new BrowserWindow({
+ width: 1024,
+ height: 768
+ })
+
+ // display the index.html file
+ mainWindow.loadURL(`file://${ __dirname }/src/static/index.html`)
+
+ // open dev tools by default so we can see any console errors
+ mainWindow.webContents.openDevTools()
+
+ mainWindow.on('closed', function () {
+ mainWindow = null
+ })
+}
+
+/* Mac Specific things */
+
+// when you close all the windows on a non-mac OS it quits the app
+app.on('window-all-closed', () => {
+ if (process.platform !== 'darwin') { app.quit() }
+})
+
+// if there is no mainWindow it creates one (like when you click the dock icon)
+app.on('activate', () => {
+ if (mainWindow === null) { createWindow() }
+})
+```
+
+This code is well commented. When we run this file, it will create a window of the specified size and
+create other application lifecycle methods. Notice the line:
+`mainWindow.loadURL(`file://${ __dirname }/src/static/index.html`)`
+That's where we tell our electron app to load the html file we created earlier.
+
+```
+electron main.js
+```
+
+Glorious.
+
+# Elm
+Now for the fun part. Here's how this goes when electron isn't in the picture.
+
+- write some elm code
+- run `elm make Main.elm --output bundle.js` which transpiles the elm code into javascript
+- import bundle.js into javascript and embed it into a div in your html.
+
+That's still the general flow. Let's do this for real.
+
+```
+touch src/elm/Main.elm
+```
+
+Finally some Elm code.
+
+```
+module Main exposing (..)
+
+import Html exposing (text)
+
+
+main =
+ text "Hello Electron. I'm Elm."
+```
+
+Now let's turn this Elm code into javascript. Elm will also install some packages.
+
+```
+elm make src/elm/Main.elm --output src/static/bundle.js
+```
+
+The output file, bundle.js, is being put into the static folder. This is temporary for convenience.
+It's easier to import into the html file for lazy people since it is in the same directory. Many future
+decisions, but not all, are driven by laziness. But only when quality isn't at stake.
+
+Edit the index.html file to import the new bundle.js file and embed it into a div.
+
+```
+<html>
+
+ <head>
+ <title>This title shows at the top</title>
+ </head>
+
+ <body>
+ <div id='container'></div>
+ <script src="bundle.js"></script>
+ <script>
+ var Elm = require('./bundle.js');
+ var container = document.getElementById('container');
+ var app = Elm.Main.embed(container);
+ </script>
+</html>
+```
+
+Here you are grabbing the container div and embedding the javascript code in there. Elm automatically
+creates the Main.embed function during transpilation.
+
+A few more housekeeping items before we see this in action. Run
+
+When you ran `elm make` some Elm packages were downloaded and an elm-package.json file was created.
+All is good, except elm doesn't know where to look for your elm files. Update the elm-package.json source-directories
+to point to the location of your elm files.
+
+```
+"source-directories": [
+ "src/elm"
+],
+```
+
+Run `electron main.js` and you'll see "Hello Electron. I'm Elm."
+
+This setup is all you need to build electron apps in Elm. However, you'll be missing out on some of the
+latest and greatest web dev tools like hot reloading and automatic transpilation. That's where webpack
+comes in.
+
+# Webpack
+
+Webpack is hard to wrap your head around, but it's awesome so let's try to understand it.
+
+What is webpack?
+The modern web stack is made up of many different parts. You could be using coffeescript or elm or
+clojurescript, sass or less, jade, etc. And they all depend on each other. A coffee file might be
+importing another coffee file, which is being used by a jade file. The dependency graph can get wild.
+Webpack takes all your files and automatically transforms them in to static assets - a clean set of
+javascript and css files.
+
+In our case, webpack is going to take all of our Elm, javascript, css (or sass if that's how you roll) files
+and turn them in to static modules. Note that webpack can only consume javascript, so we need to use
+loaders that convert our elm code into javascript.
+
+Let's get our Elm file working using webpack rather than reading the output of `elm make`.
+
+[Install webpack](http://webpack.github.io/docs/installation.html)
+
+You can use webpack from the command line, but to do anything serious (like converting Elm to js)
+you need a config file. We already created one: *webpack.config.js*. Let's create a directory for the
+webpack output file so things don't get messy in the root of our project. While we're at it, let's create
+an index.js file because as I mentioned earlier, webpack can only consume js. It can't import our index.html
+file unless we convert that to js with a loader.
+
+```
+mkdir dist
+touch src/static/index.js
+rm src/static/bundle.js
+```
+
+I removed the bundle.js file we created earlier. That will no longer be used because we are
+going to use the one that webpack makes in /dist/bundle.js.
+
+And now edit the webpack.config.js file.
+
+```
+module.exports = {
+ entry: './src/static/index.js',
+ output: {
+ path: './dist',
+ filename: 'bundle.js'
+ }
+}
+```
+
+That seems scary, but it's not so bad. Every time you run the *webpack* command, webpack will
+check this file to see what it should do. We are saying that webpack should look for the index.js
+file, do its magic, and then export a file named bundle.js to the /dist directory.
+
+Our index.js file is empty right now. It should include the javascript we used to embed Elm into
+the div. Remove that js from the html file and put it in index.js like so.
+
+```
+var Elm = require('../elm/Main');
+var container = document.getElementById('container');
+var app = Elm.Main.embed(container);
+```
+
+This code is mostly the same except you need to require the Main.elm file directly. Run *webpack*
+and see what happens. *Error: cannot resolve ... elm/Main ...* or something like that. What its
+saying is that webpack doesn't know how to consume an Elm file. We need a loader to convert the
+Elm file to a js file.
+
+```
+$ npm install --save elm-webpack-loader
+```
+
+And now configure webpack to use the loader.
+
+```
+module.exports = {
+ entry: './src/static/index.js',
+ output: {
+ path: './dist',
+ filename: 'bundle.js'
+ },
+ module: {
+ loaders: [
+ {
+ test: /\.elm$/,
+ exclude: [/elm-stuff/, /node_modules/],
+ loader: 'elm-webpack?verbose=true&warn=true',
+ }
+ ]
+ },
+ resolve: {
+ extensions: ['', '.js', '.elm']
+ }
+}
+```
+
+The elm loader is going to transpile the .elm files into .js before webpack does its bundling magic.
+Under the hood webpack uses *elm make* just like we did above. We could skip using the loader if we
+wanted to manually make the elm files every time, but webpack automates it for us now.
+webpack dev server
+
+If you run *webpack* now, you'll see the bundle file get created in /dist, but when you run electron
+you won't see the "Hello Electron. I'm Elm." text. The reason is because the html file that electron
+is running is not importing the new bundle.js file. In fact, it's not importing anything. Let's change
+that.
+
+```
+<html>
+
+ <head>
+ <title>This title shows at the top</title>
+ </head>
+
+ <body>
+ <div id='container'></div>
+ <script src='../../dist/bundle.js'></script>
+</html>
+```
+
+Try running `electron main.js` now and you'll see the message!
+
+Here's an overview:
+- Elm files go to webpack loader and get turned into js
+- New js files are consumed by webpack and turned into one file, bundle.js
+- Electron opens our index.html file, which imports the new bundle.js
+
+
+
+// add $ to commands
+
+
+
+
+
diff --git a/elm-package.json b/elm-package.json
new file mode 100644
index 0000000..19e9b02
--- /dev/null
+++ b/elm-package.json
@@ -0,0 +1,15 @@
+{
+ "version": "1.0.0",
+ "summary": "helpful summary of your project, less than 80 characters",
+ "repository": "https://github.com/user/project.git",
+ "license": "BSD3",
+ "source-directories": [
+ "src/elm"
+ ],
+ "exposed-modules": [],
+ "dependencies": {
+ "elm-lang/core": "4.0.5 <= v < 5.0.0",
+ "elm-lang/html": "1.1.0 <= v < 2.0.0"
+ },
+ "elm-version": "0.17.1 <= v < 0.18.0"
+}
diff --git a/main.js b/main.js
new file mode 100644
index 0000000..3884136
--- /dev/null
+++ b/main.js
@@ -0,0 +1,39 @@
+'use strict'
+const electron = require('electron')
+
+const app = electron.app // this is our app
+const BrowserWindow = electron.BrowserWindow // This is a Module that creates windows
+
+let mainWindow // saves a global reference to mainWindow so it doesn't get garbage collected
+
+app.on('ready', createWindow) // called when electron has initialized
+
+// This will create our app window, no surprise there
+function createWindow () {
+ mainWindow = new BrowserWindow({
+ width: 1024,
+ height: 768
+ })
+
+ // display the index.html file
+ mainWindow.loadURL(`file://${ __dirname }/src/static/index.html`)
+
+ // open dev tools by default so we can see any console errors
+ mainWindow.webContents.openDevTools()
+
+ mainWindow.on('closed', function () {
+ mainWindow = null
+ })
+}
+
+/* Mac Specific things */
+
+// when you close all the windows on a non-mac OS it quits the app
+app.on('window-all-closed', () => {
+ if (process.platform !== 'darwin') { app.quit() }
+})
+
+// if there is no mainWindow it creates one (like when you click the dock icon)
+app.on('activate', () => {
+ if (mainWindow === null) { createWindow() }
+}) \ No newline at end of file
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..d05d0d4
--- /dev/null
+++ b/package.json
@@ -0,0 +1,19 @@
+{
+ "name": "elm-electron-webpack",
+ "version": "1.0.0",
+ "description": "A guide, not a template, for building electron apps with elm and webpack.",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [
+ "elm",
+ "webpack",
+ "electron"
+ ],
+ "author": "John Omar",
+ "license": "ISC",
+ "dependencies": {
+ "elm-webpack-loader": "^3.0.6"
+ }
+}
diff --git a/src/elm/Main.elm b/src/elm/Main.elm
new file mode 100644
index 0000000..21bf797
--- /dev/null
+++ b/src/elm/Main.elm
@@ -0,0 +1,7 @@
+module Main exposing (..)
+
+import Html exposing (text)
+
+
+main =
+ text "Hello Electron. I'm Elm."
diff --git a/src/static/bundle.js b/src/static/bundle.js
new file mode 100644
index 0000000..3ac37f2
--- /dev/null
+++ b/src/static/bundle.js
@@ -0,0 +1,7446 @@
+
+(function() {
+'use strict';
+
+function F2(fun)
+{
+ function wrapper(a) { return function(b) { return fun(a,b); }; }
+ wrapper.arity = 2;
+ wrapper.func = fun;
+ return wrapper;
+}
+
+function F3(fun)
+{
+ function wrapper(a) {
+ return function(b) { return function(c) { return fun(a, b, c); }; };
+ }
+ wrapper.arity = 3;
+ wrapper.func = fun;
+ return wrapper;
+}
+
+function F4(fun)
+{
+ function wrapper(a) { return function(b) { return function(c) {
+ return function(d) { return fun(a, b, c, d); }; }; };
+ }
+ wrapper.arity = 4;
+ wrapper.func = fun;
+ return wrapper;
+}
+
+function F5(fun)
+{
+ function wrapper(a) { return function(b) { return function(c) {
+ return function(d) { return function(e) { return fun(a, b, c, d, e); }; }; }; };
+ }
+ wrapper.arity = 5;
+ wrapper.func = fun;
+ return wrapper;
+}
+
+function F6(fun)
+{
+ function wrapper(a) { return function(b) { return function(c) {
+ return function(d) { return function(e) { return function(f) {
+ return fun(a, b, c, d, e, f); }; }; }; }; };
+ }
+ wrapper.arity = 6;
+ wrapper.func = fun;
+ return wrapper;
+}
+
+function F7(fun)
+{
+ function wrapper(a) { return function(b) { return function(c) {
+ return function(d) { return function(e) { return function(f) {
+ return function(g) { return fun(a, b, c, d, e, f, g); }; }; }; }; }; };
+ }
+ wrapper.arity = 7;
+ wrapper.func = fun;
+ return wrapper;
+}
+
+function F8(fun)
+{
+ function wrapper(a) { return function(b) { return function(c) {
+ return function(d) { return function(e) { return function(f) {
+ return function(g) { return function(h) {
+ return fun(a, b, c, d, e, f, g, h); }; }; }; }; }; }; };
+ }
+ wrapper.arity = 8;
+ wrapper.func = fun;
+ return wrapper;
+}
+
+function F9(fun)
+{
+ function wrapper(a) { return function(b) { return function(c) {
+ return function(d) { return function(e) { return function(f) {
+ return function(g) { return function(h) { return function(i) {
+ return fun(a, b, c, d, e, f, g, h, i); }; }; }; }; }; }; }; };
+ }
+ wrapper.arity = 9;
+ wrapper.func = fun;
+ return wrapper;
+}
+
+function A2(fun, a, b)
+{
+ return fun.arity === 2
+ ? fun.func(a, b)
+ : fun(a)(b);
+}
+function A3(fun, a, b, c)
+{
+ return fun.arity === 3
+ ? fun.func(a, b, c)
+ : fun(a)(b)(c);
+}
+function A4(fun, a, b, c, d)
+{
+ return fun.arity === 4
+ ? fun.func(a, b, c, d)
+ : fun(a)(b)(c)(d);
+}
+function A5(fun, a, b, c, d, e)
+{
+ return fun.arity === 5
+ ? fun.func(a, b, c, d, e)
+ : fun(a)(b)(c)(d)(e);
+}
+function A6(fun, a, b, c, d, e, f)
+{
+ return fun.arity === 6
+ ? fun.func(a, b, c, d, e, f)
+ : fun(a)(b)(c)(d)(e)(f);
+}
+function A7(fun, a, b, c, d, e, f, g)
+{
+ return fun.arity === 7
+ ? fun.func(a, b, c, d, e, f, g)
+ : fun(a)(b)(c)(d)(e)(f)(g);
+}
+function A8(fun, a, b, c, d, e, f, g, h)
+{
+ return fun.arity === 8
+ ? fun.func(a, b, c, d, e, f, g, h)
+ : fun(a)(b)(c)(d)(e)(f)(g)(h);
+}
+function A9(fun, a, b, c, d, e, f, g, h, i)
+{
+ return fun.arity === 9
+ ? fun.func(a, b, c, d, e, f, g, h, i)
+ : fun(a)(b)(c)(d)(e)(f)(g)(h)(i);
+}
+
+//import Native.List //
+
+var _elm_lang$core$Native_Array = function() {
+
+// A RRB-Tree has two distinct data types.
+// Leaf -> "height" is always 0
+// "table" is an array of elements
+// Node -> "height" is always greater than 0
+// "table" is an array of child nodes
+// "lengths" is an array of accumulated lengths of the child nodes
+
+// M is the maximal table size. 32 seems fast. E is the allowed increase
+// of search steps when concatting to find an index. Lower values will
+// decrease balancing, but will increase search steps.
+var M = 32;
+var E = 2;
+
+// An empty array.
+var empty = {
+ ctor: '_Array',
+ height: 0,
+ table: []
+};
+
+
+function get(i, array)
+{
+ if (i < 0 || i >= length(array))
+ {
+ throw new Error(
+ 'Index ' + i + ' is out of range. Check the length of ' +
+ 'your array first or use getMaybe or getWithDefault.');
+ }
+ return unsafeGet(i, array);
+}
+
+
+function unsafeGet(i, array)
+{
+ for (var x = array.height; x > 0; x--)
+ {
+ var slot = i >> (x * 5);
+ while (array.lengths[slot] <= i)
+ {
+ slot++;
+ }
+ if (slot > 0)
+ {
+ i -= array.lengths[slot - 1];
+ }
+ array = array.table[slot];
+ }
+ return array.table[i];
+}
+
+
+// Sets the value at the index i. Only the nodes leading to i will get
+// copied and updated.
+function set(i, item, array)
+{
+ if (i < 0 || length(array) <= i)
+ {
+ return array;
+ }
+ return unsafeSet(i, item, array);
+}
+
+
+function unsafeSet(i, item, array)
+{
+ array = nodeCopy(array);
+
+ if (array.height === 0)
+ {
+ array.table[i] = item;
+ }
+ else
+ {
+ var slot = getSlot(i, array);
+ if (slot > 0)
+ {
+ i -= array.lengths[slot - 1];
+ }
+ array.table[slot] = unsafeSet(i, item, array.table[slot]);
+ }
+ return array;
+}
+
+
+function initialize(len, f)
+{
+ if (len <= 0)
+ {
+ return empty;
+ }
+ var h = Math.floor( Math.log(len) / Math.log(M) );
+ return initialize_(f, h, 0, len);
+}
+
+function initialize_(f, h, from, to)
+{
+ if (h === 0)
+ {
+ var table = new Array((to - from) % (M + 1));
+ for (var i = 0; i < table.length; i++)
+ {
+ table[i] = f(from + i);
+ }
+ return {
+ ctor: '_Array',
+ height: 0,
+ table: table
+ };
+ }
+
+ var step = Math.pow(M, h);
+ var table = new Array(Math.ceil((to - from) / step));
+ var lengths = new Array(table.length);
+ for (var i = 0; i < table.length; i++)
+ {
+ table[i] = initialize_(f, h - 1, from + (i * step), Math.min(from + ((i + 1) * step), to));
+ lengths[i] = length(table[i]) + (i > 0 ? lengths[i-1] : 0);
+ }
+ return {
+ ctor: '_Array',
+ height: h,
+ table: table,
+ lengths: lengths
+ };
+}
+
+function fromList(list)
+{
+ if (list.ctor === '[]')
+ {
+ return empty;
+ }
+
+ // Allocate M sized blocks (table) and write list elements to it.
+ var table = new Array(M);
+ var nodes = [];
+ var i = 0;
+
+ while (list.ctor !== '[]')
+ {
+ table[i] = list._0;
+ list = list._1;
+ i++;
+
+ // table is full, so we can push a leaf containing it into the
+ // next node.
+ if (i === M)
+ {
+ var leaf = {
+ ctor: '_Array',
+ height: 0,
+ table: table
+ };
+ fromListPush(leaf, nodes);
+ table = new Array(M);
+ i = 0;
+ }
+ }
+
+ // Maybe there is something left on the table.
+ if (i > 0)
+ {
+ var leaf = {
+ ctor: '_Array',
+ height: 0,
+ table: table.splice(0, i)
+ };
+ fromListPush(leaf, nodes);
+ }
+
+ // Go through all of the nodes and eventually push them into higher nodes.
+ for (var h = 0; h < nodes.length - 1; h++)
+ {
+ if (nodes[h].table.length > 0)
+ {
+ fromListPush(nodes[h], nodes);
+ }
+ }
+
+ var head = nodes[nodes.length - 1];
+ if (head.height > 0 && head.table.length === 1)
+ {
+ return head.table[0];
+ }
+ else
+ {
+ return head;
+ }
+}
+
+// Push a node into a higher node as a child.
+function fromListPush(toPush, nodes)
+{
+ var h = toPush.height;
+
+ // Maybe the node on this height does not exist.
+ if (nodes.length === h)
+ {
+ var node = {
+ ctor: '_Array',
+ height: h + 1,
+ table: [],
+ lengths: []
+ };
+ nodes.push(node);
+ }
+
+ nodes[h].table.push(toPush);
+ var len = length(toPush);
+ if (nodes[h].lengths.length > 0)
+ {
+ len += nodes[h].lengths[nodes[h].lengths.length - 1];
+ }
+ nodes[h].lengths.push(len);
+
+ if (nodes[h].table.length === M)
+ {
+ fromListPush(nodes[h], nodes);
+ nodes[h] = {
+ ctor: '_Array',
+ height: h + 1,
+ table: [],
+ lengths: []
+ };
+ }
+}
+
+// Pushes an item via push_ to the bottom right of a tree.
+function push(item, a)
+{
+ var pushed = push_(item, a);
+ if (pushed !== null)
+ {
+ return pushed;
+ }
+
+ var newTree = create(item, a.height);
+ return siblise(a, newTree);
+}
+
+// Recursively tries to push an item to the bottom-right most
+// tree possible. If there is no space left for the item,
+// null will be returned.
+function push_(item, a)
+{
+ // Handle resursion stop at leaf level.
+ if (a.height === 0)
+ {
+ if (a.table.length < M)
+ {
+ var newA = {
+ ctor: '_Array',
+ height: 0,
+ table: a.table.slice()
+ };
+ newA.table.push(item);
+ return newA;
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ // Recursively push
+ var pushed = push_(item, botRight(a));
+
+ // There was space in the bottom right tree, so the slot will
+ // be updated.
+ if (pushed !== null)
+ {
+ var newA = nodeCopy(a);
+ newA.table[newA.table.length - 1] = pushed;
+ newA.lengths[newA.lengths.length - 1]++;
+ return newA;
+ }
+
+ // When there was no space left, check if there is space left
+ // for a new slot with a tree which contains only the item
+ // at the bottom.
+ if (a.table.length < M)
+ {
+ var newSlot = create(item, a.height - 1);
+ var newA = nodeCopy(a);
+ newA.table.push(newSlot);
+ newA.lengths.push(newA.lengths[newA.lengths.length - 1] + length(newSlot));
+ return newA;
+ }
+ else
+ {
+ return null;
+ }
+}
+
+// Converts an array into a list of elements.
+function toList(a)
+{
+ return toList_(_elm_lang$core$Native_List.Nil, a);
+}
+
+function toList_(list, a)
+{
+ for (var i = a.table.length - 1; i >= 0; i--)
+ {
+ list =
+ a.height === 0
+ ? _elm_lang$core$Native_List.Cons(a.table[i], list)
+ : toList_(list, a.table[i]);
+ }
+ return list;
+}
+
+// Maps a function over the elements of an array.
+function map(f, a)
+{
+ var newA = {
+ ctor: '_Array',
+ height: a.height,
+ table: new Array(a.table.length)
+ };
+ if (a.height > 0)
+ {
+ newA.lengths = a.lengths;
+ }
+ for (var i = 0; i < a.table.length; i++)
+ {
+ newA.table[i] =
+ a.height === 0
+ ? f(a.table[i])
+ : map(f, a.table[i]);
+ }
+ return newA;
+}
+
+// Maps a function over the elements with their index as first argument.
+function indexedMap(f, a)
+{
+ return indexedMap_(f, a, 0);
+}
+
+function indexedMap_(f, a, from)
+{
+ var newA = {
+ ctor: '_Array',
+ height: a.height,
+ table: new Array(a.table.length)
+ };
+ if (a.height > 0)
+ {
+ newA.lengths = a.lengths;
+ }
+ for (var i = 0; i < a.table.length; i++)
+ {
+ newA.table[i] =
+ a.height === 0
+ ? A2(f, from + i, a.table[i])
+ : indexedMap_(f, a.table[i], i == 0 ? from : from + a.lengths[i - 1]);
+ }
+ return newA;
+}
+
+function foldl(f, b, a)
+{
+ if (a.height === 0)
+ {
+ for (var i = 0; i < a.table.length; i++)
+ {
+ b = A2(f, a.table[i], b);
+ }
+ }
+ else
+ {
+ for (var i = 0; i < a.table.length; i++)
+ {
+ b = foldl(f, b, a.table[i]);
+ }
+ }
+ return b;
+}
+
+function foldr(f, b, a)
+{
+ if (a.height === 0)
+ {
+ for (var i = a.table.length; i--; )
+ {
+ b = A2(f, a.table[i], b);
+ }
+ }
+ else
+ {
+ for (var i = a.table.length; i--; )
+ {
+ b = foldr(f, b, a.table[i]);
+ }
+ }
+ return b;
+}
+
+// TODO: currently, it slices the right, then the left. This can be
+// optimized.
+function slice(from, to, a)
+{
+ if (from < 0)
+ {
+ from += length(a);
+ }
+ if (to < 0)
+ {
+ to += length(a);
+ }
+ return sliceLeft(from, sliceRight(to, a));
+}
+
+function sliceRight(to, a)
+{
+ if (to === length(a))
+ {
+ return a;
+ }
+
+ // Handle leaf level.
+ if (a.height === 0)
+ {
+ var newA = { ctor:'_Array', height:0 };
+ newA.table = a.table.slice(0, to);
+ return newA;
+ }
+
+ // Slice the right recursively.
+ var right = getSlot(to, a);
+ var sliced = sliceRight(to - (right > 0 ? a.lengths[right - 1] : 0), a.table[right]);
+
+ // Maybe the a node is not even needed, as sliced contains the whole slice.
+ if (right === 0)
+ {
+ return sliced;
+ }
+
+ // Create new node.
+ var newA = {
+ ctor: '_Array',
+ height: a.height,
+ table: a.table.slice(0, right),
+ lengths: a.lengths.slice(0, right)
+ };
+ if (sliced.table.length > 0)
+ {
+ newA.table[right] = sliced;
+ newA.lengths[right] = length(sliced) + (right > 0 ? newA.lengths[right - 1] : 0);
+ }
+ return newA;
+}
+
+function sliceLeft(from, a)
+{
+ if (from === 0)
+ {
+ return a;
+ }
+
+ // Handle leaf level.
+ if (a.height === 0)
+ {
+ var newA = { ctor:'_Array', height:0 };
+ newA.table = a.table.slice(from, a.table.length + 1);
+ return newA;
+ }
+
+ // Slice the left recursively.
+ var left = getSlot(from, a);
+ var sliced = sliceLeft(from - (left > 0 ? a.lengths[left - 1] : 0), a.table[left]);
+
+ // Maybe the a node is not even needed, as sliced contains the whole slice.
+ if (left === a.table.length - 1)
+ {
+ return sliced;
+ }
+
+ // Create new node.
+ var newA = {
+ ctor: '_Array',
+ height: a.height,
+ table: a.table.slice(left, a.table.length + 1),
+ lengths: new Array(a.table.length - left)
+ };
+ newA.table[0] = sliced;
+ var len = 0;
+ for (var i = 0; i < newA.table.length; i++)
+ {
+ len += length(newA.table[i]);
+ newA.lengths[i] = len;
+ }
+
+ return newA;
+}
+
+// Appends two trees.
+function append(a,b)
+{
+ if (a.table.length === 0)
+ {
+ return b;
+ }
+ if (b.table.length === 0)
+ {
+ return a;
+ }
+
+ var c = append_(a, b);
+
+ // Check if both nodes can be crunshed together.
+ if (c[0].table.length + c[1].table.length <= M)
+ {
+ if (c[0].table.length === 0)
+ {
+ return c[1];
+ }
+ if (c[1].table.length === 0)
+ {
+ return c[0];
+ }
+
+ // Adjust .table and .lengths
+ c[0].table = c[0].table.concat(c[1].table);
+ if (c[0].height > 0)
+ {
+ var len = length(c[0]);
+ for (var i = 0; i < c[1].lengths.length; i++)
+ {
+ c[1].lengths[i] += len;
+ }
+ c[0].lengths = c[0].lengths.concat(c[1].lengths);
+ }
+
+ return c[0];
+ }
+
+ if (c[0].height > 0)
+ {
+ var toRemove = calcToRemove(a, b);
+ if (toRemove > E)
+ {
+ c = shuffle(c[0], c[1], toRemove);
+ }
+ }
+
+ return siblise(c[0], c[1]);
+}
+
+// Returns an array of two nodes; right and left. One node _may_ be empty.
+function append_(a, b)
+{
+ if (a.height === 0 && b.height === 0)
+ {
+ return [a, b];
+ }
+
+ if (a.height !== 1 || b.height !== 1)
+ {
+ if (a.height === b.height)
+ {
+ a = nodeCopy(a);
+ b = nodeCopy(b);
+ var appended = append_(botRight(a), botLeft(b));
+
+ insertRight(a, appended[1]);
+ insertLeft(b, appended[0]);
+ }
+ else if (a.height > b.height)
+ {
+ a = nodeCopy(a);
+ var appended = append_(botRight(a), b);
+
+ insertRight(a, appended[0]);
+ b = parentise(appended[1], appended[1].height + 1);
+ }
+ else
+ {
+ b = nodeCopy(b);
+ var appended = append_(a, botLeft(b));
+
+ var left = appended[0].table.length === 0 ? 0 : 1;
+ var right = left === 0 ? 1 : 0;
+ insertLeft(b, appended[left]);</