diff --git a/.gitignore b/.gitignore
index 5148e52..344e998 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,37 +1,7 @@
-# Logs
-logs
-*.log
-npm-debug.log*
-
-# Runtime data
-pids
-*.pid
-*.seed
-
-# Directory for instrumented libs generated by jscoverage/JSCover
-lib-cov
-
-# Coverage directory used by tools like istanbul
-coverage
-
-# nyc test coverage
-.nyc_output
-
-# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
-.grunt
-
-# node-waf configuration
-.lock-wscript
-
-# Compiled binary addons (http://nodejs.org/api/addons.html)
-build/Release
-
-# Dependency directories
-node_modules
-jspm_packages
-
-# Optional npm cache directory
-.npm
-
-# Optional REPL history
-.node_repl_history
+/bower_components/
+/node_modules/
+/.pulp-cache/
+/output/
+/example/index.js
+/.psc*
+/.psa*
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..cd72472
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,15 @@
+language: node_js
+dist: trusty
+sudo: required
+node_js: stable
+install:
+ - npm install
+ - npm install -g bower
+ - bower install
+script:
+ - npm run build:browser
+after_success:
+- >-
+ test $TRAVIS_TAG &&
+ echo $GITHUB_TOKEN | pulp login &&
+ echo y | pulp publish --no-push
diff --git a/bower.json b/bower.json
new file mode 100644
index 0000000..3b76200
--- /dev/null
+++ b/bower.json
@@ -0,0 +1,24 @@
+{
+ "name": "purescript-leafletjs-halogen",
+ "ignore": [
+ "**/.*",
+ "node_modules",
+ "bower_components",
+ "output"
+ ],
+ "license": "Apache-2.0",
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/slamdata/purescript-leafletjs-halogen.git"
+ },
+ "dependencies": {
+ "purescript-eff": "^3.1.0",
+ "purescript-halogen": "^2.0.1",
+ "purescript-leafletjs": "^0.2.0",
+ "purescript-halogen-css": "^6.0.0"
+ },
+ "devDependencies": {
+ "purescript-debug": "^3.0.0",
+ "purescript-random": "^3.0.0"
+ }
+}
diff --git a/example/entry.js b/example/entry.js
new file mode 100644
index 0000000..90c5896
--- /dev/null
+++ b/example/entry.js
@@ -0,0 +1 @@
+require("./../output/Main/index.js").main();
diff --git a/example/index.html b/example/index.html
new file mode 100644
index 0000000..31a067a
--- /dev/null
+++ b/example/index.html
@@ -0,0 +1,15 @@
+
+
+
+
+ purescript-halogen-leaflet
+
+
+
+
+
+
+
+
diff --git a/example/leaflet.css b/example/leaflet.css
new file mode 100644
index 0000000..c6d920a
--- /dev/null
+++ b/example/leaflet.css
@@ -0,0 +1,624 @@
+/* required styles */
+
+.leaflet-pane,
+.leaflet-tile,
+.leaflet-marker-icon,
+.leaflet-marker-shadow,
+.leaflet-tile-container,
+.leaflet-pane > svg,
+.leaflet-pane > canvas,
+.leaflet-zoom-box,
+.leaflet-image-layer,
+.leaflet-layer {
+ position: absolute;
+ left: 0;
+ top: 0;
+ }
+.leaflet-container {
+ overflow: hidden;
+ }
+.leaflet-tile,
+.leaflet-marker-icon,
+.leaflet-marker-shadow {
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ user-select: none;
+ -webkit-user-drag: none;
+ }
+/* Safari renders non-retina tile on retina better with this, but Chrome is worse */
+.leaflet-safari .leaflet-tile {
+ image-rendering: -webkit-optimize-contrast;
+ }
+/* hack that prevents hw layers "stretching" when loading new tiles */
+.leaflet-safari .leaflet-tile-container {
+ width: 1600px;
+ height: 1600px;
+ -webkit-transform-origin: 0 0;
+ }
+.leaflet-marker-icon,
+.leaflet-marker-shadow {
+ display: block;
+ }
+/* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */
+/* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */
+.leaflet-container .leaflet-overlay-pane svg,
+.leaflet-container .leaflet-marker-pane img,
+.leaflet-container .leaflet-shadow-pane img,
+.leaflet-container .leaflet-tile-pane img,
+.leaflet-container img.leaflet-image-layer {
+ max-width: none !important;
+ }
+
+.leaflet-container.leaflet-touch-zoom {
+ -ms-touch-action: pan-x pan-y;
+ touch-action: pan-x pan-y;
+ }
+.leaflet-container.leaflet-touch-drag {
+ -ms-touch-action: pinch-zoom;
+ }
+.leaflet-container.leaflet-touch-drag.leaflet-touch-zoom {
+ -ms-touch-action: none;
+ touch-action: none;
+}
+.leaflet-tile {
+ filter: inherit;
+ visibility: hidden;
+ }
+.leaflet-tile-loaded {
+ visibility: inherit;
+ }
+.leaflet-zoom-box {
+ width: 0;
+ height: 0;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ z-index: 800;
+ }
+/* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */
+.leaflet-overlay-pane svg {
+ -moz-user-select: none;
+ }
+
+.leaflet-pane { z-index: 400; }
+
+.leaflet-tile-pane { z-index: 200; }
+.leaflet-overlay-pane { z-index: 400; }
+.leaflet-shadow-pane { z-index: 500; }
+.leaflet-marker-pane { z-index: 600; }
+.leaflet-tooltip-pane { z-index: 650; }
+.leaflet-popup-pane { z-index: 700; }
+
+.leaflet-map-pane canvas { z-index: 100; }
+.leaflet-map-pane svg { z-index: 200; }
+
+.leaflet-vml-shape {
+ width: 1px;
+ height: 1px;
+ }
+.lvml {
+ behavior: url(#default#VML);
+ display: inline-block;
+ position: absolute;
+ }
+
+
+/* control positioning */
+
+.leaflet-control {
+ position: relative;
+ z-index: 800;
+ pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
+ pointer-events: auto;
+ }
+.leaflet-top,
+.leaflet-bottom {
+ position: absolute;
+ z-index: 1000;
+ pointer-events: none;
+ }
+.leaflet-top {
+ top: 0;
+ }
+.leaflet-right {
+ right: 0;
+ }
+.leaflet-bottom {
+ bottom: 0;
+ }
+.leaflet-left {
+ left: 0;
+ }
+.leaflet-control {
+ float: left;
+ clear: both;
+ }
+.leaflet-right .leaflet-control {
+ float: right;
+ }
+.leaflet-top .leaflet-control {
+ margin-top: 10px;
+ }
+.leaflet-bottom .leaflet-control {
+ margin-bottom: 10px;
+ }
+.leaflet-left .leaflet-control {
+ margin-left: 10px;
+ }
+.leaflet-right .leaflet-control {
+ margin-right: 10px;
+ }
+
+
+/* zoom and fade animations */
+
+.leaflet-fade-anim .leaflet-tile {
+ will-change: opacity;
+ }
+.leaflet-fade-anim .leaflet-popup {
+ opacity: 0;
+ -webkit-transition: opacity 0.2s linear;
+ -moz-transition: opacity 0.2s linear;
+ -o-transition: opacity 0.2s linear;
+ transition: opacity 0.2s linear;
+ }
+.leaflet-fade-anim .leaflet-map-pane .leaflet-popup {
+ opacity: 1;
+ }
+.leaflet-zoom-animated {
+ -webkit-transform-origin: 0 0;
+ -ms-transform-origin: 0 0;
+ transform-origin: 0 0;
+ }
+.leaflet-zoom-anim .leaflet-zoom-animated {
+ will-change: transform;
+ }
+.leaflet-zoom-anim .leaflet-zoom-animated {
+ -webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1);
+ -moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1);
+ -o-transition: -o-transform 0.25s cubic-bezier(0,0,0.25,1);
+ transition: transform 0.25s cubic-bezier(0,0,0.25,1);
+ }
+.leaflet-zoom-anim .leaflet-tile,
+.leaflet-pan-anim .leaflet-tile {
+ -webkit-transition: none;
+ -moz-transition: none;
+ -o-transition: none;
+ transition: none;
+ }
+
+.leaflet-zoom-anim .leaflet-zoom-hide {
+ visibility: hidden;
+ }
+
+
+/* cursors */
+
+.leaflet-interactive {
+ cursor: pointer;
+ }
+.leaflet-grab {
+ cursor: -webkit-grab;
+ cursor: -moz-grab;
+ }
+.leaflet-crosshair,
+.leaflet-crosshair .leaflet-interactive {
+ cursor: crosshair;
+ }
+.leaflet-popup-pane,
+.leaflet-control {
+ cursor: auto;
+ }
+.leaflet-dragging .leaflet-grab,
+.leaflet-dragging .leaflet-grab .leaflet-interactive,
+.leaflet-dragging .leaflet-marker-draggable {
+ cursor: move;
+ cursor: -webkit-grabbing;
+ cursor: -moz-grabbing;
+ }
+
+/* marker & overlays interactivity */
+.leaflet-marker-icon,
+.leaflet-marker-shadow,
+.leaflet-image-layer,
+.leaflet-pane > svg path,
+.leaflet-tile-container {
+ pointer-events: none;
+ }
+
+.leaflet-marker-icon.leaflet-interactive,
+.leaflet-image-layer.leaflet-interactive,
+.leaflet-pane > svg path.leaflet-interactive {
+ pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
+ pointer-events: auto;
+ }
+
+/* visual tweaks */
+
+.leaflet-container {
+ background: #ddd;
+ outline: 0;
+ }
+.leaflet-container a {
+ color: #0078A8;
+ }
+.leaflet-container a.leaflet-active {
+ outline: 2px solid orange;
+ }
+.leaflet-zoom-box {
+ border: 2px dotted #38f;
+ background: rgba(255,255,255,0.5);
+ }
+
+
+/* general typography */
+.leaflet-container {
+ font: 12px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif;
+ }
+
+
+/* general toolbar styles */
+
+.leaflet-bar {
+ box-shadow: 0 1px 5px rgba(0,0,0,0.65);
+ border-radius: 4px;
+ }
+.leaflet-bar a,
+.leaflet-bar a:hover {
+ background-color: #fff;
+ border-bottom: 1px solid #ccc;
+ width: 26px;
+ height: 26px;
+ line-height: 26px;
+ display: block;
+ text-align: center;
+ text-decoration: none;
+ color: black;
+ }
+.leaflet-bar a,
+.leaflet-control-layers-toggle {
+ background-position: 50% 50%;
+ background-repeat: no-repeat;
+ display: block;
+ }
+.leaflet-bar a:hover {
+ background-color: #f4f4f4;
+ }
+.leaflet-bar a:first-child {
+ border-top-left-radius: 4px;
+ border-top-right-radius: 4px;
+ }
+.leaflet-bar a:last-child {
+ border-bottom-left-radius: 4px;
+ border-bottom-right-radius: 4px;
+ border-bottom: none;
+ }
+.leaflet-bar a.leaflet-disabled {
+ cursor: default;
+ background-color: #f4f4f4;
+ color: #bbb;
+ }
+
+.leaflet-touch .leaflet-bar a {
+ width: 30px;
+ height: 30px;
+ line-height: 30px;
+ }
+
+
+/* zoom control */
+
+.leaflet-control-zoom-in,
+.leaflet-control-zoom-out {
+ font: bold 18px 'Lucida Console', Monaco, monospace;
+ text-indent: 1px;
+ }
+.leaflet-control-zoom-out {
+ font-size: 20px;
+ }
+
+.leaflet-touch .leaflet-control-zoom-in {
+ font-size: 22px;
+ }
+.leaflet-touch .leaflet-control-zoom-out {
+ font-size: 24px;
+ }
+
+
+/* layers control */
+
+.leaflet-control-layers {
+ box-shadow: 0 1px 5px rgba(0,0,0,0.4);
+ background: #fff;
+ border-radius: 5px;
+ }
+.leaflet-control-layers-toggle {
+ background-image: url(images/layers.png);
+ width: 36px;
+ height: 36px;
+ }
+.leaflet-retina .leaflet-control-layers-toggle {
+ background-image: url(images/layers-2x.png);
+ background-size: 26px 26px;
+ }
+.leaflet-touch .leaflet-control-layers-toggle {
+ width: 44px;
+ height: 44px;
+ }
+.leaflet-control-layers .leaflet-control-layers-list,
+.leaflet-control-layers-expanded .leaflet-control-layers-toggle {
+ display: none;
+ }
+.leaflet-control-layers-expanded .leaflet-control-layers-list {
+ display: block;
+ position: relative;
+ }
+.leaflet-control-layers-expanded {
+ padding: 6px 10px 6px 6px;
+ color: #333;
+ background: #fff;
+ }
+.leaflet-control-layers-scrollbar {
+ overflow-y: scroll;
+ padding-right: 5px;
+ }
+.leaflet-control-layers-selector {
+ margin-top: 2px;
+ position: relative;
+ top: 1px;
+ }
+.leaflet-control-layers label {
+ display: block;
+ }
+.leaflet-control-layers-separator {
+ height: 0;
+ border-top: 1px solid #ddd;
+ margin: 5px -10px 5px -6px;
+ }
+
+/* Default icon URLs */
+.leaflet-default-icon-path {
+ background-image: url(images/marker-icon.png);
+ }
+
+
+/* attribution and scale controls */
+
+.leaflet-container .leaflet-control-attribution {
+ background: #fff;
+ background: rgba(255, 255, 255, 0.7);
+ margin: 0;
+ }
+.leaflet-control-attribution,
+.leaflet-control-scale-line {
+ padding: 0 5px;
+ color: #333;
+ }
+.leaflet-control-attribution a {
+ text-decoration: none;
+ }
+.leaflet-control-attribution a:hover {
+ text-decoration: underline;
+ }
+.leaflet-container .leaflet-control-attribution,
+.leaflet-container .leaflet-control-scale {
+ font-size: 11px;
+ }
+.leaflet-left .leaflet-control-scale {
+ margin-left: 5px;
+ }
+.leaflet-bottom .leaflet-control-scale {
+ margin-bottom: 5px;
+ }
+.leaflet-control-scale-line {
+ border: 2px solid #777;
+ border-top: none;
+ line-height: 1.1;
+ padding: 2px 5px 1px;
+ font-size: 11px;
+ white-space: nowrap;
+ overflow: hidden;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+
+ background: #fff;
+ background: rgba(255, 255, 255, 0.5);
+ }
+.leaflet-control-scale-line:not(:first-child) {
+ border-top: 2px solid #777;
+ border-bottom: none;
+ margin-top: -2px;
+ }
+.leaflet-control-scale-line:not(:first-child):not(:last-child) {
+ border-bottom: 2px solid #777;
+ }
+
+.leaflet-touch .leaflet-control-attribution,
+.leaflet-touch .leaflet-control-layers,
+.leaflet-touch .leaflet-bar {
+ box-shadow: none;
+ }
+.leaflet-touch .leaflet-control-layers,
+.leaflet-touch .leaflet-bar {
+ border: 2px solid rgba(0,0,0,0.2);
+ background-clip: padding-box;
+ }
+
+
+/* popup */
+
+.leaflet-popup {
+ position: absolute;
+ text-align: center;
+ margin-bottom: 20px;
+ }
+.leaflet-popup-content-wrapper {
+ padding: 1px;
+ text-align: left;
+ border-radius: 12px;
+ }
+.leaflet-popup-content {
+ margin: 13px 19px;
+ line-height: 1.4;
+ }
+.leaflet-popup-content p {
+ margin: 18px 0;
+ }
+.leaflet-popup-tip-container {
+ width: 40px;
+ height: 20px;
+ position: absolute;
+ left: 50%;
+ margin-left: -20px;
+ overflow: hidden;
+ pointer-events: none;
+ }
+.leaflet-popup-tip {
+ width: 17px;
+ height: 17px;
+ padding: 1px;
+
+ margin: -10px auto 0;
+
+ -webkit-transform: rotate(45deg);
+ -moz-transform: rotate(45deg);
+ -ms-transform: rotate(45deg);
+ -o-transform: rotate(45deg);
+ transform: rotate(45deg);
+ }
+.leaflet-popup-content-wrapper,
+.leaflet-popup-tip {
+ background: white;
+ color: #333;
+ box-shadow: 0 3px 14px rgba(0,0,0,0.4);
+ }
+.leaflet-container a.leaflet-popup-close-button {
+ position: absolute;
+ top: 0;
+ right: 0;
+ padding: 4px 4px 0 0;
+ border: none;
+ text-align: center;
+ width: 18px;
+ height: 14px;
+ font: 16px/14px Tahoma, Verdana, sans-serif;
+ color: #c3c3c3;
+ text-decoration: none;
+ font-weight: bold;
+ background: transparent;
+ }
+.leaflet-container a.leaflet-popup-close-button:hover {
+ color: #999;
+ }
+.leaflet-popup-scrolled {
+ overflow: auto;
+ border-bottom: 1px solid #ddd;
+ border-top: 1px solid #ddd;
+ }
+
+.leaflet-oldie .leaflet-popup-content-wrapper {
+ zoom: 1;
+ }
+.leaflet-oldie .leaflet-popup-tip {
+ width: 24px;
+ margin: 0 auto;
+
+ -ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";
+ filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);
+ }
+.leaflet-oldie .leaflet-popup-tip-container {
+ margin-top: -1px;
+ }
+
+.leaflet-oldie .leaflet-control-zoom,
+.leaflet-oldie .leaflet-control-layers,
+.leaflet-oldie .leaflet-popup-content-wrapper,
+.leaflet-oldie .leaflet-popup-tip {
+ border: 1px solid #999;
+ }
+
+
+/* div icon */
+
+.leaflet-div-icon {
+ background: #fff;
+ border: 1px solid #666;
+ }
+
+
+/* Tooltip */
+/* Base styles for the element that has a tooltip */
+.leaflet-tooltip {
+ position: absolute;
+ padding: 6px;
+ background-color: #fff;
+ border: 1px solid #fff;
+ border-radius: 3px;
+ color: #222;
+ white-space: nowrap;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ pointer-events: none;
+ box-shadow: 0 1px 3px rgba(0,0,0,0.4);
+ }
+.leaflet-tooltip.leaflet-clickable {
+ cursor: pointer;
+ pointer-events: auto;
+ }
+.leaflet-tooltip-top:before,
+.leaflet-tooltip-bottom:before,
+.leaflet-tooltip-left:before,
+.leaflet-tooltip-right:before {
+ position: absolute;
+ pointer-events: none;
+ border: 6px solid transparent;
+ background: transparent;
+ content: "";
+ }
+
+/* Directions */
+
+.leaflet-tooltip-bottom {
+ margin-top: 6px;
+}
+.leaflet-tooltip-top {
+ margin-top: -6px;
+}
+.leaflet-tooltip-bottom:before,
+.leaflet-tooltip-top:before {
+ left: 50%;
+ margin-left: -6px;
+ }
+.leaflet-tooltip-top:before {
+ bottom: 0;
+ margin-bottom: -12px;
+ border-top-color: #fff;
+ }
+.leaflet-tooltip-bottom:before {
+ top: 0;
+ margin-top: -12px;
+ margin-left: -6px;
+ border-bottom-color: #fff;
+ }
+.leaflet-tooltip-left {
+ margin-left: -6px;
+}
+.leaflet-tooltip-right {
+ margin-left: 6px;
+}
+.leaflet-tooltip-left:before,
+.leaflet-tooltip-right:before {
+ top: 50%;
+ margin-top: -6px;
+ }
+.leaflet-tooltip-left:before {
+ right: 0;
+ margin-right: -12px;
+ border-left-color: #fff;
+ }
+.leaflet-tooltip-right:before {
+ left: 0;
+ margin-left: -12px;
+ border-right-color: #fff;
+ }
diff --git a/example/marker.svg b/example/marker.svg
new file mode 100644
index 0000000..ba1af69
--- /dev/null
+++ b/example/marker.svg
@@ -0,0 +1,19 @@
+
+
+
diff --git a/example/src/Main.purs b/example/src/Main.purs
new file mode 100644
index 0000000..6474841
--- /dev/null
+++ b/example/src/Main.purs
@@ -0,0 +1,160 @@
+module Main where
+
+import Prelude
+
+import Control.MonadPlus (class MonadPlus)
+import Control.Monad.Aff (Aff)
+import Control.Monad.Aff.Class (class MonadAff, liftAff)
+import Control.Monad.Eff (Eff)
+import Control.Monad.Eff.Class (liftEff)
+import Control.Monad.Eff.Random (RANDOM, random)
+import Control.Monad.Rec.Class (class MonadRec)
+
+import Data.Array as A
+import Data.Either (Either(..))
+import Data.Traversable as F
+import Data.Maybe (Maybe(..), isNothing)
+import Data.Newtype (under)
+import Data.Path.Pathy ((>), (<.>), file, currentDir, rootDir, dir)
+import Data.Profunctor (lmap)
+import Data.URI as URI
+import Data.URI (URIRef)
+
+import Graphics.Canvas (CANVAS)
+
+import Halogen.Aff as HA
+import Halogen.VDom.Driver (runUI)
+import Halogen.Component as HC
+import Halogen.Component.Profunctor as HPR
+import Halogen as H
+import Halogen.HTML as HH
+import Halogen.HTML.Events as HE
+
+import Leaflet.Halogen as HL
+import Leaflet.Core as LC
+import Leaflet.Plugin.Heatmap as LH
+import Leaflet.Util ((×))
+
+data Query a
+ = HandleMessage Slot HL.Message a
+ | SetWidth a
+ | AddMarker a
+ | RemoveMarker a
+
+type State =
+ { marker ∷ Maybe LC.Marker
+ , firstSize ∷ { width ∷ Int, height ∷ Int }
+ , secondSize ∷ { width ∷ Int, height ∷ Int }
+ }
+
+type Input = Unit
+
+type Slot = Int
+
+type Effects = HA.HalogenEffects (HL.Effects (canvas ∷ CANVAS, random ∷ RANDOM))
+type MainAff = Aff Effects
+type HTML = H.ParentHTML Query HL.Query Slot MainAff
+type DSL = H.ParentDSL State Query HL.Query Slot Void MainAff
+
+initialState ∷ Input → State
+initialState _ =
+ { marker: Nothing
+ , firstSize: { width: 400, height: 600 }
+ , secondSize: { width: 400, height: 600 }
+ }
+
+ui ∷ H.Component HH.HTML Query Unit Void MainAff
+ui = H.parentComponent
+ { initialState
+ , render
+ , eval
+ , receiver: const Nothing
+ }
+ where
+ leaflet =
+ HC.unComponent (\cfg →
+ HC.mkComponent cfg{ receiver = \{width, height} →
+ Just $ H.action $ HL.SetDimension { width: Just width, height: Just height } } )
+ $ under HPR.ProComponent (lmap $ const unit) HL.leaflet
+
+ render ∷ State → HTML
+ render state =
+ HH.div_
+ [ HH.slot 0 leaflet state.firstSize (HE.input $ HandleMessage 0)
+ , HH.button [ HE.onClick (HE.input_ SetWidth) ][ HH.text "resize me" ]
+ , HH.button [ HE.onClick (HE.input_ AddMarker) ] [ HH.text "add marker" ]
+ , HH.button [ HE.onClick (HE.input_ RemoveMarker) ] [ HH.text "remove marker" ]
+ , HH.slot 1 leaflet state.secondSize (HE.input $ HandleMessage 1)
+ ]
+
+ eval ∷ Query ~> DSL
+ eval = case _ of
+ HandleMessage 0 (HL.Initialized _) next → do
+ tiles ← LC.tileLayer osmURI
+ void $ H.query 0 $ H.action $ HL.AddLayers [ LC.tileToLayer tiles ]
+ pure next
+ HandleMessage _ (HL.Initialized leaf) next → do
+ tiles ← LC.tileLayer osmURI
+ heatmap ← LC.layer
+ heatmapData ← liftAff mkHeatmapData
+ layState ← LH.mkHeatmap LH.defaultOptions heatmapData heatmap leaf
+ void $ H.query 1 $ H.action $ HL.AddLayers [ LC.tileToLayer tiles, heatmap ]
+ pure next
+ SetWidth next → do
+ H.modify _{ firstSize = { height: 200, width: 1000 } }
+ pure next
+ AddMarker next → do
+ state ← H.get
+ when (isNothing state.marker) do
+ latLng ← liftAff $ LC.mkLatLng (-37.87) 175.457
+ icon ← LC.icon iconConf
+ marker ← LC.marker latLng >>= LC.setIcon icon
+ H.modify _{ marker = Just marker }
+ void $ H.query 0 $ H.action $ HL.AddLayers [ LC.markerToLayer marker ]
+ pure next
+ RemoveMarker next → do
+ state ← H.get
+ F.for_ state.marker \marker → do
+ void $ H.query 0 $ H.action $ HL.RemoveLayers [ LC.markerToLayer marker ]
+ H.modify _{ marker = Nothing }
+ pure next
+
+ iconConf ∷ { iconUrl ∷ URIRef, iconSize ∷ LC.Point }
+ iconConf =
+ { iconUrl: Right $ URI.RelativeRef
+ (URI.RelativePart Nothing $ Just $ Right $ currentDir > file "marker" <.> "svg")
+ Nothing
+ Nothing
+ , iconSize: 40 × 40
+ }
+
+ osmURI ∷ URIRef
+ osmURI =
+ Left $ URI.URI
+ (Just $ URI.URIScheme "http")
+ (URI.HierarchicalPart
+ (Just $ URI.Authority Nothing [(URI.NameAddress "{s}.tile.osm.org") × Nothing])
+ (Just $ Right $ rootDir > dir "{z}" > dir "{x}" > file "{y}" <.> "png"))
+ Nothing
+ Nothing
+
+ mkHeatmapData
+ ∷ ∀ m
+ . MonadAff Effects m
+ ⇒ MonadPlus m
+ ⇒ MonadRec m
+ ⇒ m (Array { lat ∷ LC.Degrees, lng ∷ LC.Degrees, i ∷ Number })
+ mkHeatmapData = do
+ let
+ inp = A.range 0 10000
+ foldFn acc _ = do
+ xDiff ← liftEff random
+ lat ← LC.mkDegrees $ xDiff / 30.0 - 37.87
+ yDiff ← liftEff random
+ lng ← LC.mkDegrees $ yDiff / 40.0 + 175.457
+ i ← map (_ / 2.0) $ liftEff random
+ pure $ A.snoc acc { lat, lng, i }
+ A.foldRecM foldFn [] inp
+
+main ∷ Eff Effects Unit
+main = HA.runHalogenAff $ runUI ui unit =<< HA.awaitBody
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..3d43817
--- /dev/null
+++ b/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "purescript-leafletjs-halogen",
+ "scripts": {
+ "build": "pulp build -- --censor-lib --strict --stash",
+ "build:non-strict": "pulp build",
+ "run:test": "pulp test -- --censor-lib --strict --stash",
+ "run": "pulp run",
+ "build:browser": "pulp browserify --include example --to example/index.js",
+ "build:webpack": "webpack",
+ "build:dev": "webpack --watch",
+ "ide": "purs ide server"
+ },
+ "dependencies": {
+ "leaflet": "^1.0.3",
+ "pulp": "^11.0.0",
+ "purescript": "^0.11.4",
+ "purescript-psa": "^0.5.1",
+ "webpack": "^2.4.1"
+ }
+}
diff --git a/src/Leaflet/Halogen.purs b/src/Leaflet/Halogen.purs
new file mode 100644
index 0000000..7506b69
--- /dev/null
+++ b/src/Leaflet/Halogen.purs
@@ -0,0 +1,143 @@
+module Leaflet.Halogen where
+
+import Prelude
+
+import Control.Monad.Eff (kind Effect)
+import Control.Monad.Aff (delay)
+import Control.Monad.Aff.Class (class MonadAff, liftAff)
+
+import CSS.Geometry (width, height)
+import CSS.Size (px, pct)
+
+import Data.Int as Int
+import Data.Maybe (Maybe(..), fromJust)
+import Data.Traversable as F
+import Data.Time.Duration (Milliseconds(..))
+
+import DOM (DOM)
+
+import Halogen as H
+import Halogen.HTML as HH
+import Halogen.HTML.CSS (style)
+import Halogen.HTML.Properties as HP
+
+import Leaflet.Core as LC
+import Leaflet.Util ((∘))
+
+import Partial.Unsafe (unsafePartial)
+
+type State =
+ { width ∷ Int
+ , height ∷ Int
+ , view ∷ LC.LatLng
+ , zoom ∷ LC.Zoom
+ , leaflet ∷ Maybe LC.Leaflet
+ }
+
+data Query a
+ = Init a
+ | SetDimension { width ∷ Maybe Int, height ∷ Maybe Int } a
+ -- Note, layers are not preserved in state, parent component must
+ -- handle it.
+ | AddLayers (Array LC.Layer) a
+ | RemoveLayers (Array LC.Layer) a
+ | GetLeaflet (Maybe LC.Leaflet → a)
+ | SetView LC.LatLng a
+ | SetZoom LC.Zoom a
+
+data Message
+ = Initialized LC.Leaflet
+
+type Effects (e ∷ # Effect) =
+ ( dom ∷ DOM
+ | e )
+
+type HTML = H.ComponentHTML Query
+
+type DSL m = H.ComponentDSL State Query Message m
+
+type Input = Unit
+
+initialState ∷ ∀ i. i → State
+initialState i =
+ { height: 400
+ , width: 600
+ , view: unsafePartial fromJust $ LC.mkLatLng (-37.87) 175.457
+ , zoom: unsafePartial fromJust $ LC.mkZoom 12
+ , leaflet: Nothing
+ }
+
+leaflet
+ ∷ ∀ e m
+ . MonadAff (Effects e) m
+ ⇒ H.Component HH.HTML Query Input Message m
+leaflet = H.lifecycleComponent
+ { initialState
+ , render
+ , eval
+ , initializer: Just $ H.action Init
+ , finalizer: Nothing
+ , receiver: const Nothing
+ }
+
+render ∷ State → HTML
+render state =
+ HH.div
+ [ style do
+ height $ px $ Int.toNumber state.height
+ width $ px $ Int.toNumber state.width
+ ]
+ [ HH.div
+ [ HP.ref $ H.RefLabel "leaflet"
+ , style do
+ height $ pct 100.0
+ width $ pct 100.0
+ ] [ ] ]
+
+
+eval ∷ ∀ e m. MonadAff (Effects e) m ⇒ Query ~> DSL m
+eval = case _ of
+ Init next → do
+ state ← H.get
+ void $ H.getHTMLElementRef (H.RefLabel "leaflet")
+ >>= F.traverse \el → do
+ leaf ← LC.leaflet el
+ >>= LC.setView state.view
+ >>= LC.setZoom state.zoom
+ H.modify _{ leaflet = Just leaf }
+ H.raise $ Initialized leaf
+ pure next
+ SetDimension dim next → do
+ state ← H.get
+ F.for_ dim.height \h →
+ when (h >= 0) $ H.modify _{ height = h }
+ F.for_ dim.width \w →
+ when (w >= 0) $ H.modify _{ width = w }
+ F.for_ state.leaflet \l → do
+ liftAff $ delay $ Milliseconds 1000.0
+ LC.invalidateSize true l
+ pure next
+ AddLayers ls next → do
+ state ← H.get
+ F.for_ state.leaflet \leaf →
+ F.for_ ls \layer →
+ void $ LC.addLayer layer leaf
+ pure next
+ RemoveLayers ls next → do
+ state ← H.get
+ F.for_ state.leaflet \leaf →
+ F.for_ ls \layer →
+ void $ LC.removeLayer layer leaf
+ pure next
+ GetLeaflet continue → do
+ H.gets $ continue ∘ _.leaflet
+ SetView v next → do
+ state ← H.get
+ F.for_ state.leaflet $ LC.setView v
+ H.modify _{ view = v }
+ pure next
+ SetZoom zoom next → do
+ state ← H.get
+ F.for_ state.leaflet $ LC.setZoom zoom
+ H.modify _{ zoom = zoom }
+ pure next
diff --git a/webpack.config.js b/webpack.config.js
new file mode 100644
index 0000000..817f8fc
--- /dev/null
+++ b/webpack.config.js
@@ -0,0 +1,6 @@
+module.exports = {
+ entry: "./example/entry.js",
+ output: {
+ filename: "./example/index.js"
+ }
+};