text:
### A Pluto.jl notebook ###
# v0.18.4

using Markdown
using InteractiveUtils

# This Pluto notebook uses @bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of @bind gives bound variables a default value (instead of an error).
macro bind(def, element)
    quote
        local iv = try Base.loaded_modules[Base.PkgId(Base.UUID("6e696c72-6542-2067-7265-42206c756150"), "AbstractPlutoDingetjes")].Bonds.initial_value catch; b -> missing; end
        local el = $(esc(element))
        global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : iv(el)
        el
    end
end

# ╔═╡ a0fb4f28-bfe4-4877-bf07-31acb9a56d2c
using HypertextLiteral

# ╔═╡ 57232d88-b74f-4823-be61-8db450c93f5c
using Markdown: withtag, htmlesc

# ╔═╡ e8c5ba24-10e9-49e8-8c11-0add092637f8
"""
	@skip_as_script expression

Marks a expression as Pluto-only, which means that it won't be executed when running outside Pluto. Do not use this for your own projects.
"""
macro skip_as_script(ex) skip_as_script(__module__) ? esc(ex) : nothing end

# ╔═╡ d738b448-387b-4942-af82-cc93042705a4
function skip_as_script(m::Module)
	if isdefined(m, :PlutoForceDisplay)
		return m.PlutoForceDisplay
	else
		isdefined(m, :PlutoRunner) && parentmodule(m) == Main
	end
end

# ╔═╡ e1bbe1d7-68ef-4ee1-8174-d1ae1f822acb
macro only_as_script(ex) skip_as_script(__module__) ? nothing : esc(ex) end

# ╔═╡ 81adbd39-5780-4cc6-a53f-a4472bacf1c0
if skip_as_script(@__MODULE__)
	import Pkg
	Pkg.activate(Base.current_project(@__DIR__))
	Text("Project env active")
end

# ╔═╡ d8f907cd-2f89-4d54-a311-998dc8ee148e
teststr = "<x>\"\"woa"

# ╔═╡ ac542b84-dbc8-47e2-8835-9e43582b6ad7
import Random: randstring

# ╔═╡ 6da84fb9-a629-4e4c-819e-dd87a3e267ce
import Dates

# ╔═╡ dc3b6628-f453-46d9-b6a1-957608a20764
import AbstractPlutoDingetjes

# ╔═╡ a203d9d4-cd7b-4368-9f6d-e040a5757565
import AbstractPlutoDingetjes.Bonds

# ╔═╡ d088bcdb-d851-4ad7-b5a0-751c1f348995
begin
	struct OldSlider
		range::AbstractRange
		default::Real
		show_value::Bool
	end

	OldSlider(range::AbstractRange; default=missing, show_value=false) = OldSlider(range, (default === missing) ? first(range) : default, show_value)

	function Base.show(io::IO, m::MIME"text/html", slider::OldSlider)
		show(io, m,
			@htl("""<input $((
					type="range",
					min=first(slider.range),
					step=step(slider.range),
					max=last(slider.range),
					value=slider.default,
					oninput=(slider.show_value ? "this.nextElementSibling.value=this.value" : ""),
				))>$(
					slider.show_value ?
						@htl("<output>$(slider.default)</output>") :
						nothing
				)"""))
	end

	Base.get(slider::OldSlider) = slider.default
	Bonds.initial_value(slider::OldSlider) = slider.default
	Bonds.possible_values(slider::OldSlider) = slider.range
	function Bonds.validate_value(slider::OldSlider, val)
		val isa Real && (minimum(slider.range) - 0.0001 <= val <= maximum(slider.range) + 0.0001)
	end

	OldSlider
end

# ╔═╡ e286f877-8b3c-4c74-a37c-a3458d66c1f8
"""
```julia
closest(range::AbstractVector, x)
```

Return the element of `range` that is closest to `x`.
"""
function closest(range::AbstractRange, x::Real)
	rmin = minimum(range)
	rmax = maximum(range)

	if x <= rmin
		rmin
	elseif x >= rmax
		rmax
	else
		rstep = step(range)

		int_val = (x - rmin) / rstep
		range[round(Int, int_val) + 1]
	end
end

# ╔═╡ db3aefaa-9539-4c46-ad9b-83763f9ef624
# Like `argmin` in Julia 1.7
argmin_compat(f,xs) = xs[findmin(Iterators.map(f,xs))[2]]

# ╔═╡ 97fc914b-005f-4b4d-80cb-23016d589609
function closest(values::AbstractVector{<:Real}, x::Real)
	argmin_compat(y -> abs(y - x), values)
end

# ╔═╡ 0373d633-18bd-4936-a0ae-7a4f6f05372a
closest(values::AbstractVector, x) = first(values)

# ╔═╡ 0baae341-aa0d-42fd-9f21-d40dd5a03af9
begin
	local result = begin
		"""A slider over the given values.

	## Examples
	`@bind x Slider(1:10)`

	`@bind x Slider(0.00 : 0.01 : 0.30)`

	`@bind x Slider(1:10; default=8, show_value=true)`

	`@bind x Slider(["hello", "world!"])`
	"""
	struct Slider{T <: Any}
		values::AbstractVector{T}
		default::T
		show_value::Bool
	end
	end

	function Slider(values::AbstractVector{T}; default=missing, show_value=false) where T
		Slider(values, (default === missing) ? first(values) : let
			d = default
			d ∈ values ? convert(T, d) : closest(values, d)
		end, show_value)
	end

	function Base.show(io::IO, m::MIME"text/html", slider::Slider)

		# compat code
		if !AbstractPlutoDingetjes.is_supported_by_display(io, Bonds.transform_value)
			compat_slider = try
				OldSlider(slider.values, slider.default, slider.show_value)
			catch
				HTML("❌ You need to update Pluto to use this PlutoUI element.")
			end
			return show(io, m, compat_slider)
		end

		start_index = findfirst(isequal(slider.default), slider.values)

		show(io, m, @htl(
			"""<input $((
				type="range",
				min=1,
				max=length(slider.values),
				value=start_index,
			))>$(
					slider.show_value ? @htl(
					"""<output style='
						font-family: system-ui;
    					font-size: 15px;
    					margin-left: 3px;
    					transform: translateY(-4px);
    					display: inline-block;'>$(string(slider.default))</output>"""
				) : nothing
			)"""))
	end

	Base.get(slider::Slider) = slider.default
	Bonds.initial_value(slider::Slider) = slider.default

	Bonds.possible_values(slider::Slider) = 1:length(slider.values)

	function Bonds.transform_value(slider::Slider, val_from_js)
		slider.values[val_from_js]
	end

	function Bonds.validate_value(slider::Slider, val)
		val isa Integer && 1 <= val <= length(slider.values)
	end

	result
end

# ╔═╡ e440a357-1656-4cc4-8191-146fe82fbc8c
@bind os3 HTML(repr(MIME"text/html"(), Slider(0:.1:1, default=.5, show_value=true)))

# ╔═╡ 629e5d68-580f-4d6b-be14-5a109091e6b7
HTML(repr(MIME"text/html"(), Slider([sin, cos])))

# ╔═╡ f59eef32-4732-46db-87b0-3564433ce43e
begin
	local result = begin
	"""A box where you can type in a number, within a specific range.

	## Examples
	`@bind x NumberField(1:10)`

	`@bind x NumberField(0.00 : 0.01 : 0.30)`

	`@bind x NumberField(1:10; default=8)`

	"""
	struct NumberField
		range::AbstractRange
		default::Number
	end
	end

	NumberField(range::AbstractRange{<:T}; default=missing) where T = NumberField(range, (default === missing) ? first(range) : let
		d = default
		d ∈ range ? convert(T, d) : closest(range, d)
	end)

	function Base.show(io::IO, m::MIME"text/html", numberfield::NumberField)
		show(io, m, @htl("""<input $((
				type="number",
				min=first(numberfield.range),
				step=step(numberfield.range),
				max=last(numberfield.range),
				value=numberfield.default
			))>"""))
	end

	Base.get(numberfield::NumberField) = numberfield.default
	Bonds.initial_value(nf::NumberField) = nf.default
	Bonds.possible_values(nf::NumberField) = nf.range
	Bonds.transform_value(nf::NumberField, val) = Base.convert(eltype(nf.range), val)
	function Bonds.validate_value(nf::NumberField, val)
		val isa Real && (minimum(nf.range) - 0.0001 <= val <= maximum(nf.range) + 0.0001)
	end

	result
end

# ╔═╡ b7c21c22-17f5-44b8-98de-a261d5c7192b
begin
	local result = begin
	"""A button that sends back the same value every time that it is clicked.

	You can use it to _trigger reactive cells_.

	**See also [`CounterButton`](@ref)** to get the *number of times* the button was clicked.

	## Examples

	In one cell:

	```julia
	@bind go Button("Go!")
	```

	and in a second cell:

	```julia
	begin
		# reference the bound variable - clicking the button will run this cell
		go

		md"My favorite number is \$(rand())!"
	end
	```
	"""
	struct LabelButton
		label::AbstractString
	end
	end
	LabelButton() = LabelButton("Click")

	function Base.show(io::IO, m::MIME"text/html", button::LabelButton)
		show(io, m, @htl("""<input type="button" value="$(button.label)">"""))
	end

	Base.get(button::LabelButton) = button.label
	Bonds.initial_value(b::LabelButton) = b.label
	Bonds.possible_values(b::LabelButton) = [b.label]
	function Bonds.validate_value(b::LabelButton, val)
		val == b.label
	end

	result
end

# ╔═╡ 7f8e4abf-e7e7-47bc-b1cc-514fa1af106c
const Button = LabelButton

# ╔═╡ 3ae2351b-ac4a-4669-bb11-39a1c029b301
Button()

# ╔═╡ 548bda96-2461-48a3-a3ad-6d113337826e
begin
	local result = begin
	"""A button that sends back the number of times that it was clicked.

	You can use it to _trigger reactive cells_.

	## Examples

	In one cell:

	```julia
	@bind go CounterButton("Go!")
	```

	and in a second cell:

	```julia
	begin
		# reference the bound variable - clicking the button will run this cell
		go

		md"My favorite number is \$(rand())!"
	end
	```
	"""
	struct CounterButton
		label::AbstractString
	end
	end
	CounterButton() = CounterButton("Click")

	function Base.show(io::IO, m::MIME"text/html", button::CounterButton)
		show(io, m, @htl(
			"""<input type="button" value=$(button.label)>"""))
	end

	Base.get(button::CounterButton) = button.label
	Bonds.initial_value(b::CounterButton) = 0
	Bonds.possible_values(b::CounterButton) = Bonds.InfinitePossibilities()
	function Bonds.validate_value(b::CounterButton, val)
		val isa Integer && val >= 0
	end

	result
end

# ╔═╡ 76c3b77b-08aa-4899-bbdd-4f8faa8d1486
begin
	local result = begin
	"""A checkbox to choose a Boolean value `true`/`false`.

	## Examples

	`@bind programming_is_fun CheckBox()`

	`@bind julia_is_fun CheckBox(default=true)`

	`md"Would you like the thing? \$(@bind enable_thing CheckBox())"`
	"""
	struct CheckBox
		default::Bool
	end
	end

	CheckBox(;default::Bool=false) = CheckBox(default)

	function Base.show(io::IO, ::MIME"text/html", button::CheckBox)
		print(io, """<input type="checkbox"$(button.default ? " checked" : "")>""")
	end

	Base.get(checkbox::CheckBox) = checkbox.default
	Bonds.initial_value(b::CheckBox) = b.default
	Bonds.possible_values(b::CheckBox) = [false, true]
	function Bonds.validate_value(b::CheckBox, val)
		val isa Bool
	end

	result
end

# ╔═╡ f81bb386-203b-4392-b974-a1e2146b1a08
begin
	local result = begin
	"""
	```julia
	# single-line:
	TextField(default="", placeholder=nothing)

	# with specified size:
	TextField(size; default="", placeholder=nothing)
	```

	A text input - the user can type text, the text is returned as `String` via `@bind`.

	# Keyword arguments
	- `default`: the initial value
	- `placeholder`: a value to display when the text input is empty

	# `size` argument
	- If `size` is a tuple `(cols::Integer, row::Integer)`, a **multi-line** `<textarea>` will be shown, with the given dimensions.
	- If `size` is an integer, it controls the **width** of the single-line input.

	# Examples
	```julia
	@bind author TextField()
	```

	```julia
	@bind poem TextField((30,5); default="Hello\\nJuliaCon!")
	```
	"""
	struct TextField
		dims::Union{Tuple{Integer,Integer},Integer,Nothing}
		default::AbstractString
		placeholder::Union{AbstractString,Nothing}
	end
	end

	TextField(dims::Union{Tuple{Integer,Integer},Integer,Nothing}=nothing; default::AbstractString="", placeholder::Union{AbstractString,Nothing}=nothing) = TextField(dims, default, placeholder)
	TextField(dims, default) = TextField(dims, default, nothing)

	function Base.show(io::IO, m::MIME"text/html", t::TextField)
		show(io, m,
		if t.dims === nothing || t.dims isa Integer
			@htl("""<input $((
				type="text",
				value=t.default,
				placeholder=t.placeholder,
				size=t.dims
			))>""")
		else
			@htl("""<textarea $((
				cols=t.dims[1],
				rows=t.dims[2],
				placeholder=t.placeholder,
			))>$(t.default)</textarea>""")
		end
		)
	end

	Base.get(t::TextField) = t.default
	Bonds.initial_value(t::TextField) = t.default
	Bonds.possible_values(t::TextField) = Bonds.InfinitePossibilities()
	function Bonds.validate_value(t::TextField, val)
		val isa AbstractString
	end

	result
end

# ╔═╡ 0b46ba0f-f6ff-4df2-bd2b-aeacda9e8865
@htl("<input type=text maxlength=4>")

# ╔═╡ f4c5199a-e195-42ed-b398-4197b2e85aec
TextField(4)

# ╔═╡ 4363f31e-1d71-4ad8-bfe8-04403d2d3621
TextField((30,2), default=teststr);

# ╔═╡ 121dc1e7-080e-48dd-9105-afa5f7886fb7
TextField(placeholder="Type something here!")

# ╔═╡ 13ed4bfd-7bfa-49dd-a212-d7f6564af8e2
TextField((5,5),placeholder="Type something here!")

# ╔═╡ c9614498-54a8-4925-9353-7a13d3303916
begin
	Base.@kwdef struct PasswordField
		default::AbstractString=""
	end
	local result = begin
	@doc """A password input (`<input type="password">`) - the user can type text, the text is returned as `String` via `@bind`.

	This does not provide any special security measures, it just renders black dots (•••) instead of the typed characters.

	Use `default` to set the initial value.

	See the [Mozilla docs about `<input type="password">`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/password)

	# Examples
	`@bind secret_poem PasswordField()`

	`@bind secret_poem PasswordField(default="Te dansen omdat men leeft")`"""
	PasswordField
	end

	function Base.show(io::IO, m::MIME"text/html", passwordfield::PasswordField)
		show(io, m, @htl("""<input type="password" value=$(passwordfield.default)>"""))
	end

	Base.get(passwordfield::PasswordField) = passwordfield.default
	Bonds.initial_value(t::PasswordField) = t.default
	Bonds.possible_values(t::PasswordField) = Bonds.InfinitePossibilities()
	function Bonds.validate_value(t::PasswordField, val)
		val isa AbstractString
	end

	result
end

# ╔═╡ edfdbaee-ec31-40c2-9ad5-28250fe6b651
begin
	struct OldSelect
		options::Vector{Pair{<:AbstractString,<:Any}}
		default::Union{Missing, AbstractString}
	end

	OldSelect(options::AbstractVector{<:AbstractString}; default=missing) = OldSelect([o => o for o in options], default)

	OldSelect(options::AbstractVector{<:Pair{<:AbstractString,<:Any}}; default=missing) = OldSelect(options, default)

	function Base.show(io::IO, m::MIME"text/html", select::OldSelect)
		show(io, m, @htl(
			"""<select>$(
		map(select.options) do o
				@htl(
				"<option value=$(o.first) selected=$(!ismissing(select.default) && o.first == select.default)>$(
				o.second
				)</option>")
			end
		)</select>"""))
	end

	OldSelect
end

# ╔═╡ eb4e17fd-07ba-4031-a39f-0d9fccd3d886
begin
	local result = begin
	"""
	```julia
	Select(options::Vector; [default])
	# or with a custom display value:
	Select(options::Vector{Pair{Any,String}}; [default::Any])
	```

	A dropdown menu - the user can choose an element of the `options` vector.

	See [`MultiSelect`](@ref) for a version that allows multiple selected items.

	# Examples
	```julia
	@bind veg Select(["potato", "carrot"])
	```

	```julia
	@bind f Select([sin, cos, tan, sqrt])

	f(0.5)
	```

	You can also specify a display value by giving pairs `bound_value => display_value`:

	```julia
	@bind f Select([cos => "cosine function", sin => "sine function"])

	f(0.5)
	```
	"""
	struct Select
		options::AbstractVector{Pair}
		default::Union{Missing, Any}
	end
	end

	Select(options::AbstractVector; default=missing) = Select([o => o for o in options], default)

	Select(options::AbstractVector{<:Pair}; default=missing) = Select(options, default)

	function Base.show(io::IO, m::MIME"text/html", select::Select)

		# compat code
		if !AbstractPlutoDingetjes.is_supported_by_display(io, Bonds.transform_value)
			compat_element = try
				OldSelect(select.options, select.default)
			catch
				HTML("❌ You need to update Pluto to use this PlutoUI element.")
			end
			return show(io, m, compat_element)
		end

		show(io, m, @htl(
			"""<select>$(
		map(enumerate(select.options)) do (i,o)
				@htl(
				"<option value=$(i) selected=$(!ismissing(select.default) && o.first == select.default)>$(
				string(o.second)
				)</option>")
			end
		)</select>"""))
	end

	Base.get(select::Select) = ismissing(select.default) ? first(select.options).first : select.default
	Bonds.initial_value(select::Select) = ismissing(select.default) ? first(select.options).first : select.default

	Bonds.possible_values(select::Select) = (string(i) for i in 1:length(select.options))

	function Bonds.transform_value(select::Select, val_from_js)
		# val_from_js will be a String, but let's allow Integers as well, there's no harm in that
		val_num = val_from_js isa Integer ? val_from_js : tryparse(Int64, val_from_js)
		select.options[val_num].first
	end

	function Bonds.validate_value(select::Select, val_from_js)
		# val_from_js will be a String, but let's allow Integers as well, there's no harm in that
		val_num = val_from_js isa Integer ? val_from_js : tryparse(Int64, val_from_js)
		val_num isa Integer && 1 <= val_num <= length(select.options)
	end

	result
end

# ╔═╡ d64bb805-b700-4fd6-8894-2980152ce250
Select(["a" => "✅", "b" => "🆘", "c" => "🆘"])

# ╔═╡ 4f3ba840-28ce-4790-b929-ce6af8920189
Select(["a" => "🆘", "b" => "✅", "c" => "🆘"]; default="b")

# ╔═╡ b34d3a01-f8d6-4586-b655-5da84d586cd5
OldSelect(["a" => "✅", "b" => "🆘", "c" => "🆘"])

# ╔═╡ 609ab7f4-4fc4-4122-986d-9bfe54fa715d
OldSelect(["a" => "🆘", "b" => "✅", "c" => "🆘"]; default="b")

# ╔═╡ 6459df3f-143f-4d1a-a238-4447b11cc56c
HTML(repr(MIME"text/html"(), Select(["a" => "✅", "b" => "🆘", "c" => "🆘"])))

# ╔═╡ f3bef89c-61ac-4dcf-bf47-3824f11db26f
@skip_as_script HTML(repr(MIME"text/html"(), Select([sin, cos])))

# ╔═╡ 42e9e5ab-7d34-4300-a6c0-47f5cde658d8
begin
	local result = begin
"""A group of radio buttons - the user can choose one of the `options`, an array of `String`s.

`options` can also be an array of pairs `key::String => value::Any`. The `key` is returned via `@bind`; the `value` is shown.

# Examples
`@bind veg Radio(["potato", "carrot"])`

`@bind veg Radio(["potato" => "🥔", "carrot" => "🥕"])`

`@bind veg Radio(["potato" => "🥔", "carrot" => "🥕"], default="carrot")`

"""
struct Radio
    options::Vector{Pair{<:AbstractString,<:Any}}
    default::Union{Nothing, AbstractString}
end
	end
Radio(options::AbstractVector{<:AbstractString}; default=nothing) = Radio([o => o for o in options], default)
Radio(options::AbstractVector{<:Pair{<:AbstractString,<:Any}}; default=nothing) = Radio(options, default)

function Base.show(io::IO, m::MIME"text/html", radio::Radio)
    groupname = randstring('a':'z')

	h = @htl(
		"""<form>$(
		map(radio.options) do o
			@htl(
				"""
<input $((
					type="radio",
					id=(groupname * o.first),
					name=groupname,
					value=o.first,
					checked=(radio.default === o.first),
				))>$(
					o.second
				)
"""
			)
        end
		)</form>""")
	show(io, m, h)
end

Base.get(radio::Radio) = radio.default

	Bonds.initial_value(select::Radio) = select.default
	Bonds.possible_values(select::Radio) =
		first.(select.options)
	function Bonds.validate_value(select::Radio, val)
		val ∈ (first(p) for p in select.options)
	end
	result
end

# ╔═╡ 04ed1e71-d806-423e-b99c-476ea702feb3
Radio(["a", "b"]; default="b")

# ╔═╡ 7c4303a1-19be-41a2-a6c7-90146e01401d
md"""
nothing checked by defualt, the initial value should be `nothing`
"""

# ╔═╡ d9522557-07e6-4a51-ae92-3abe7a7d2732
r1s = [];

# ╔═╡ cc80b7eb-ca09-41ca-8015-933591378437
begin
	struct OldMultiSelect
		options::Vector{Pair{<:AbstractString,<:Any}}
		default::Union{Missing, AbstractVector{AbstractString}}
		size::Union{Missing,Int}
	end
	function Base.show(io::IO, m::MIME"text/html", select::OldMultiSelect)
		show(io, m, @htl(
			"""<select title='Cmd+Click or Ctrl+Click to select multiple items.' multiple size=$(
				coalesce(select.size, min(10, length(select.options)))
			)>$(
		map(select.options) do o
				@htl(
				"<option value=$(o.first) selected=$(!ismissing(select.default) && o.first ∈ select.default)>$(
				o.second
				)</option>")
			end
		)</select>"""))
	end
	OldMultiSelect
end

# ╔═╡ f21db694-2acb-417d-9f4d-0d2400aa067e
subarrays(x) = (
	x[collect(I)]
	for I in Iterators.product(Iterators.repeated([true,false],length(x))...) |> collect |> vec
)

# ╔═╡ 4d8ea460-ff2b-4e92-966e-89e76d4806af
subarrays([2,3,3]) |> collect

# ╔═╡ e058076f-46fc-4435-ab45-530e27c95478
begin
local result = begin
"""A file upload box. The chosen file will be read by the browser, and the bytes are sent back to Julia.

The optional `accept` argument can be an array of `MIME`s. The user can only select files with these MIME. If only `image/*` MIMEs are allowed, then smartphone browsers will open the camera instead of a file browser.

## Examples

`@bind file_data FilePicker()`

`file_data["data"]`

You can limit the allowed MIME types:

```julia
@bind image_data FilePicker([MIME("image/jpg"), MIME("image/png")])
# and use MIME groups:
@bind image_data FilePicker([MIME("image/*")])
```
"""
struct FilePicker
    accept::Array{MIME,1}
end
end
FilePicker() = FilePicker(MIME[])

function Base.show(io::IO, m::MIME"text/html", filepicker::FilePicker)
	show(io, m, @htl("""<input type='file' accept=$(
    join(string.(filepicker.accept), ",")
    )>"""))
end

Base.get(select::FilePicker) = nothing

	Bonds.initial_value(select::FilePicker) =
		nothing
	Bonds.possible_values(select::FilePicker) =
		Bonds.InfinitePossibilities()
	function Bonds.validate_value(select::FilePicker, val)
		val isa Nothing || (
			val isa Dict && keys(val) == ["name", "data", "type"] && val["data"] isa Vector{UInt8} && val["name"] isa String && val["type"] isa String
		)
	end

	result
end

# ╔═╡ db65293b-891a-43a3-8a42-b23bf542755f
FilePicker([MIME"image/png"()])

# ╔═╡ d611e6f7-c574-4f0f-a46f-48ec8cf4b5aa
begin
	local result = begin
Base.@kwdef struct DateField
    default::Union{Dates.TimeType,Nothing}=nothing
end
@doc """A date input (`<input type="date">`) - the user can pick a date, the date is returned as `Dates.DateTime` via `@bind`.

Use `default` to set the initial value.

!!! warning "Outdated"
	This is `DateField`, but you should use our new function, [`DatePicker`](@ref), which is much better! It returns a `Date` directly, instead of a `DateTime`.

# Examples
`@bind best_day_of_my_life DateField()`

`@bind best_day_of_my_life DateField(default=today())`

# See also
[`DatePicker`](@ref)
"""
DateField
	end

function Base.show(io::IO, m::MIME"text/html", datefield::DateField)
	show(io, m, @htl("<input $((
			type="date",
			value=(datefield.default === nothing ? "" : Dates.format(datefield.default, "Y-mm-dd")),
		))>"))
end
Base.get(datefield::DateField) = datefield.default === nothing ? nothing : Dates.DateTime(datefield.default)
	Bonds.initial_value(datefield::DateField) =
		datefield.default === nothing ? nothing : Dates.DateTime(datefield.default)
	Bonds.possible_values(datefield::DateField) =
		Bonds.InfinitePossibilities()

	result
end

# ╔═╡ ea7c4d05-c516-4f07-9d48-7df9ce997939
begin
local result = begin
	Base.@kwdef struct TimeField
    default::Union{Dates.TimeType,Nothing}=nothing
end

@doc """A time input (`<input type="time">`) - the user can pick a time, the time is returned as `String` via `@bind` (e.g. `"15:45"`). Value is `""` until a time is picked.

Use `default` to set the initial value.

See the [Mozilla docs about `<input type="time">`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/time)

!!! warning "Outdated"
	This is `TimeField`, but you should use our new function, [`TimePicker`](@ref), which is much better! It returns a Julia `Time` directly, instead of a `String`.

# Examples
`@bind lunch_time TimeField()`

`@bind lunch_time TimeField(default=now())`

# See also
[`TimePicker`](@ref)
"""
TimeField
end

function Base.show(io::IO, m::MIME"text/html", timefield::TimeField)
	show(io, m, @htl("<input $((
			type="time",
			value=(
				timefield.default === nothing ? nothing : Dates.format(timefield.default, "HH:MM"),
			)
		))>"))
end
Base.get(timefield::TimeField) = timefield.default === nothing ?  "" : Dates.format(timefield.default, "HH:MM")
	Bonds.initial_value(timefield::TimeField) =
		timefield.default === nothing ?  "" : Dates.format(timefield.default, "HH:MM")
	Bonds.possible_values(timefield::TimeField) =
		Bonds.InfinitePossibilities()

	result
end

# ╔═╡ 4c9c1e24-235f-44f6-83f3-9f985f7fb536
# bti

# ╔═╡ e9feb20c-3667-4ea9-9278-6b68ece1de6c
begin
local result = begin
	Base.@kwdef struct ColorStringPicker
	    default::String="#000000"
	end
@doc """A color input (`<input type="color">`) - the user can pick an RGB color, the color is returned as color hex `String` via `@bind`. The value is lowercase and starts with `#`.

Use `default` to set the initial value.

# Examples
`@bind color ColorStringPicker()`

`@bind color ColorStringPicker(default="#aabbcc")`

# See also
[`ColorPicker`](@ref)
"""
ColorStringPicker
end

function Base.show(io::IO, ::MIME"text/html", colorStringPicker::ColorStringPicker)
    withtag(() -> (), io, :input, :type=>"color", :value=>colorStringPicker.default)
end
Base.get(colorStringPicker::ColorStringPicker) = colorStringPicker.default

	Bonds.initial_value(c::ColorStringPicker) =
		c.default
	Bonds.possible_values(c::ColorStringPicker) =
		Bonds.InfinitePossibilities()
	function Bonds.validate_value(c::ColorStringPicker, val)
		val isa String && val[1] == "#"
	end

	result
end

# ╔═╡ 1d95c38d-d336-436d-a62e-0a3786c321ca
ColorStringPicker("#ffffff")

# ╔═╡ 724125f3-7699-4103-a5d8-bc6a00fab0ff
ColorStringPicker(default="#abbaff")

# ╔═╡ 632f6d08-0091-41d7-afb6-bdc7c5e4e837
import ColorTypes: RGB, N0f8, Colorant

# ╔═╡ 363236e7-fb6c-4b6f-a8a1-fef5bcadb570
function _hex_to_color(val::String)
	val[2:end]
	rgb_str = val[2:3], val[4:5], val[6:7]
	RGB{N0f8}(reinterpret(N0f8, [parse.(UInt8, rgb_str, base=16)])...)
end

# ╔═╡ b329dcff-e69b-47d3-8b05-56562416cd89
_hex_to_color("#f0f000")

# ╔═╡ 6eece14b-7034-4f12-a98a-d127459f3cdf
function _color_to_hex(val::RGB{N0f8})

	"#" * join(string.(reinterpret.(UInt8, (val.r,val.g,val.b)); base=16, pad=2))
end

# ╔═╡ e1a67bd4-7fa9-4e15-8975-2c71e704de8c
begin
	local result = begin
	Base.@kwdef struct ColorPicker{T <: RGB{N0f8}}
	    default::T=zero(RGB{N0f8})

		# ColorPicker(; default::RGB{N0f8}) = new{RGB{N0f8}}(default)
	end
	@doc """
	A color input - the user can pick an RGB color, the color is returned as a `Colorant`, a type from the package [Colors.jl](https://github.com/JuliaGraphics/Colors.jl).

	Use `default` to set the initial value.

	# Examples
	```julia
	@bind color1 ColorPicker()
	```

	```julia
	using Colors

	@bind color2 ColorPicker(default=colorant"#aabbcc")
	```
	"""
	ColorPicker
	end

	function Base.show(io::IO, m::MIME"text/html", cp::ColorPicker)
		# compat code
		if !AbstractPlutoDingetjes.is_supported_by_display(io, Bonds.transform_value)
			return show(io, m, HTML("❌ You need to update Pluto to use this PlutoUI element."))
		end
		show(io, m, @htl("<input type=color value=$(_color_to_hex(cp.default))>"))
	end

	Base.get(cp::ColorPicker) = cp.default
	Bonds.initial_value(cp::ColorPicker) = cp.default

	Bonds.possible_values(cp::ColorPicker) = Bonds.InfinitePossibilities()

	function Bonds.validate_value(cp::ColorPicker, val)
		val isa String && val[1] == "#"
	end

	function Bonds.transform_value(cp::ColorPicker, val)
		val[2:end]
		rgb_str = val[2:3], val[4:5], val[6:7]
		RGB{N0f8}(reinterpret(N0f8, [parse.(UInt8, rgb_str, base=16)])...)
	end

	result
end

# ╔═╡ 5cff9494-55d5-4154-8a57-fb73a82e2036
begin
	local result = begin
		Base.@kwdef struct _TimePicker
		    default::Union{Dates.TimeType,Nothing}=nothing
			show_seconds::Bool = false
		end
		@doc """
		```julia
		TimePicker(; [default::Dates.TimeType], [show_seconds::Bool=false])
		```

		A time input - the user can pick a time, the time is returned as a `Dates.Time`.

		Use the `default` keyword argument to set the initial value. If no initial value is given, the bound value is set to `nothing` until a time is picked.

		# Examples
		```julia
		@bind time1 TimePicker()
		```

		```julia
		import Dates
		@bind time2 TimePicker(default=Dates.Time(23,59,44))
		```
		"""
		TimePicker
	end

	TimePicker(default::Dates.TimeType) = _TimePicker(
		default=default, show_seconds=false)
	TimePicker(; kwargs...) = _TimePicker(; kwargs...)

	function Base.show(io::IO, m::MIME"text/html", tp::_TimePicker)
		if !AbstractPlutoDingetjes.is_supported_by_display(io, Bonds.transform_value)
			return show(io, m, HTML("❌ You need to update Pluto to use this PlutoUI element."))
		end
		t, step = _fmt_time(tp)
		show(io, m, @htl("<input type=time value=$(t) step=$(step)>"))
	end

	function _fmt_time(t)
		if t.show_seconds
			fmt = "HH:MM:SS"
			step = 1
		else
			fmt = "HH:MM"
			step = 0
		end
		if isnothing(t.default)
			t = nothing
		else
			t = Dates.format(t.default, fmt)
		end
		return t, step
	end

	Base.get(tp::_TimePicker) = Bonds.initial_value(tp)
	Bonds.initial_value(tp::_TimePicker) =
		Bonds.transform_value(tp, _fmt_time(tp) |> first)

	Bonds.possible_values(tp::_TimePicker) = Bonds.InfinitePossibilities()
	Bonds.transform_value(tp::_TimePicker, val) =
		(isnothing(val) || isempty(val)) ? nothing : Dates.Time(val)

	Bonds.validate_value(tp::_TimePicker, s::String) = true # if it is not a valid time string, then `Bonds.transform_value` will fail, which is a safe failure.
	result
end

# ╔═╡ 5156eed1-04a0-4fc4-95fd-11a086a57c4a
begin
	local result = begin
		Base.@kwdef struct DatePicker
			default::Union{Dates.TimeType,Nothing}=nothing
		end
	@doc """
	```julia
	DatePicker(; [default::Dates.Date])
	```

	A date input - the user can pick a date, the date is returned as a `Dates.Date`.

	Use the `default` keyword argument to set the initial value. If no initial value is given, the bound value is set to `nothing` until a date is picked.

	# Examples
	```julia
	@bind date1 DatePicker()
	```

	```julia
	using Dates

	@bind date2 DatePicker(default=Date(2022, 12, 14))
	```
	"""
	DatePicker
	end

	function Base.show(io::IO, m::MIME"text/html", dp::DatePicker)
		show(io, m, @htl("<input $((
				type="date",
				value=(dp.default === nothing ? "" : Dates.format(dp.default, "Y-mm-dd")),
			))>"))
	end

	Base.get(dp::DatePicker) = Bonds.initial_value(dp)
	Bonds.initial_value(dp::DatePicker) =
		dp.default === nothing ? nothing : Dates.Date(dp.default)

	Bonds.possible_values(dp::DatePicker) = Bonds.InfinitePossibilities()

	Bonds.transform_value(dp::DatePicker, val::Nothing) = nothing
	Bonds.transform_value(dp::DatePicker, val::Dates.TimeType) = Dates.Date(val)
	Bonds.transform_value(dp::DatePicker, val::String) =
		isempty(val) ? nothing : Dates.Date(val)

	Bonds.validate_value(dp::DatePicker,
		val::Union{Nothing,Dates.TimeType,String}) = true # see reasoning in `Bond.validate_value` in TimePicker

	result
end

# ╔═╡ 38a7533e-7b0f-4c55-ade5-5a8d879d14c7
begin
	local result = begin
	"""
	```julia
	MultiSelect(options::Vector; [default], [size::Int])
	# or with a custom display value:
	MultiSelect(options::Vector{Pair{Any,String}}; [default], [size::Int])
	```

	A multi-selector - the user can choose one or more of the `options`.

	See [`Select`](@ref) for a version that allows only one selected item.

	# Examples
	```julia
	@bind vegetables MultiSelect(["potato", "carrot"])

	if "carrot" ∈ vegetables
		"yum yum!"
	end
	```

	```julia
	@bind chosen_functions MultiSelect([sin, cos, tan, sqrt])

	[f(0.5) for f in chosen_functions]
	```

	You can also specify a display value by giving pairs `bound_value => display_value`:

	```julia
	@bind chosen_functions MultiSelect([
		cos => "cosine function",
		sin => "sine function",
	])

	[f(0.5) for f in chosen_functions]
	```

	The `size` keyword argument may be used to specify how many rows should be visible at once.

	```julia
	@bind letters MultiSelect(string.('a':'z'), size=20)
	```
	"""
	struct MultiSelect{BT,DT}
		options::AbstractVector{Pair{BT,DT}}
		default::Union{Missing, AbstractVector{BT}}
		size::Union{Missing,Int}
	end
	end
	MultiSelect(options::AbstractVector{<:Pair{BT,DT}}; default=missing, size=missing) where {BT,DT} = MultiSelect(options, default, size)
	MultiSelect(options::AbstractVector{BT}; default=missing, size=missing) where BT = MultiSelect{BT,BT}(Pair{BT,BT}[o => o for o in options], default, size)

	function Base.show(io::IO, m::MIME"text/html", select::MultiSelect)

		# compat code
		if !AbstractPlutoDingetjes.is_supported_by_display(io, Bonds.transform_value)
			compat_element = try
				OldMultiSelect(select.options, select.default, select.size)
			catch
				HTML("❌ You need to update Pluto to use this PlutoUI element.")
			end
			return show(io, m, compat_element)
		end

		show(io, m, @htl(
			"""<select title='Cmd+Click or Ctrl+Click to select multiple items.' multiple size=$(
				coalesce(select.size, min(10, length(select.options)))
			)>$(
		map(enumerate(select.options)) do (i,o)
				@htl(
				"<option value=$(i) selected=$(!ismissing(select.default) && o.first ∈ select.default)>$(
				string(o.second)
				)</option>")
			end
		)</select>"""))
	end

		Base.get(select::MultiSelect) = Bonds.initial_value(select)
		Bonds.initial_value(select::MultiSelect{BT,DT}) where {BT,DT} =
			ismissing(select.default) ? BT[] : select.default
		Bonds.possible_values(select::MultiSelect) =
			subarrays((string(i) for i in 1:length(select.options)))

		function Bonds.transform_value(select::MultiSelect{BT,DT}, val_from_js) where {BT,DT}
			# val_from_js will be a vector of Strings, but let's allow Integers as well, there's no harm in that
			@assert val_from_js isa Vector

			val_nums = (
				v isa Integer ? v : tryparse(Int64, v)
				for v in val_from_js
			)

			BT[select.options[v].first for v in val_nums]
		end

		function Bonds.validate_value(select::MultiSelect, val)
			val isa Vector && all(val_from_js) do v
				val_num = v isa Integer ? v : tryparse(Int64, v)
				1 ≤ val_num ≤ length(select.options)
			end
		end

		result
	end

# ╔═╡ c2b473f4-b56b-4a91-8377-6c86da895cbe
@bind f Slider([sin, cos, sqrt, "asdf"]; default=sqrt)

# ╔═╡ 5caa34e8-e501-4248-be65-ef9c6303d025
f

# ╔═╡ 46a90b45-8fef-493e-9bd1-a71d1f9c53f6
f(123)

# ╔═╡ 328e9651-0ad1-46ce-904c-afd7deaacf94
bs = @bind s1 Slider(1:10)

# ╔═╡ 38d32393-49be-469c-840b-b58c7339a276
bs

# ╔═╡ 75b008b2-afc0-4bd5-9183-e0e0d392a4c5
@bind s2 Slider(30:.5:40; default=38, show_value=true)

# ╔═╡ 9df251eb-b4f5-46cc-a4fe-ff2fa670b773
@bind s3 Slider([sin, cos, tan], default=cos, show_value=true)

# ╔═╡ 7c5765ae-c10a-4677-97a3-848a423cb8b9
s1, s2, s3

# ╔═╡ ec870eea-36a4-48b6-95d7-f7c083e29856
bos = @bind os1 OldSlider(1:10)

# ╔═╡ b44f1128-32a5-4d1d-a00b-446143074056
bos

# ╔═╡ f6cd1201-da84-4dee-9e88-b65fa1ff749e
@bind os2 OldSlider(0:.1:1, default=.5, show_value=true)

# ╔═╡ 05f6a603-b738-47b1-b335-acaaf480a240
os1, os2, os3

# ╔═╡ f7870d7f-992d-4d64-85aa-7621ab16244f
nf1b = @bind nf1 NumberField(1:10; default=3.2)

# ╔═╡ 893e22e1-a1e1-43cb-84fe-4931f3ba35c1
nf1b

# ╔═╡ 7089edb6-720d-4df5-b3ca-da17d48b107e
nf1

# ╔═╡ c32f42ee-0e7f-4648-99f7-21eff7b45cec
nf2b = @bind nf2 NumberField(0:.1:1; default = 0)

# ╔═╡ efc0d77c-93d5-4634-9c0b-aa16d00ec007
nf2b

# ╔═╡ 89e05f4b-c720-4ca5-a7fe-ceee0bcef9d9
nf2

# ╔═╡ c6d68308-53e7-4c60-8649-8f0161f28d70
@bind b1 Button(teststr)

# ╔═╡ c111da12-d0ca-4b9b-8ede-20f4303a1c4b
b1

# ╔═╡ cd08b524-d778-4acd-9fac-851d90df7179
@bind cb1 CounterButton()

# ╔═╡ 6135dca4-86f9-4675-8a45-fa16b3d2c3eb
cb1

# ╔═╡ 6cb75589-5496-4edd-9b21-ea49d5c0e733
bc = @bind c1 CheckBox()

# ╔═╡ bcee47b1-0f45-4649-8517-0e93fa92bfe5
bc

# ╔═╡ 73656df8-ac9f-466d-a8d0-0a2e5dbdbd8c
@bind c2 CheckBox(true)

# ╔═╡ e89ee9a3-5c78-4ff8-81e9-f44f5150d5f6
c1, c2

# ╔═╡ 1e522148-542a-4a2f-ad92-12421a6530dc
bt1 = @bind t1 TextField()

# ╔═╡ 1ac4abe2-5f06-42c6-b614-fb9a00e65386
bt1

# ╔═╡ 1d81db28-103b-4bde-9a9a-f3038ee9b10b
@bind t2 TextField(default=teststr)

# ╔═╡ e25a2ec1-5dab-461e-bc47-6b3f1fe19d30
bt2 = @bind t3 TextField((30,2), teststr)

# ╔═╡ be68f41c-0730-461c-8782-7e8d7a745509
bt2

# ╔═╡ 00145a3e-cb62-4c54-807b-8d2bce6a9fc9
t1, t2, t3

# ╔═╡ 970681ed-1c3a-4327-b636-8cb0cdd90dbb
bpe = @bind p1 PasswordField()

# ╔═╡ e6032ca6-03a5-4bda-95d2-dcd9ee6b5924
bpe

# ╔═╡ d4bf5249-6027-43c5-bd20-48ad95721e27
@bind p2 PasswordField(teststr)

# ╔═╡ d8c60294-0ca6-4cb0-b51d-9f6d6b370b28
@bind p3 PasswordField(default=teststr)

# ╔═╡ fbc6e4c1-4bd8-43a2-ac82-e6f76033fd8e
p1, p2, p3

# ╔═╡ 57a7d0c9-2f4a-44e6-9b7a-0bbd98611c9d
bse = @bind se1 Select(["a" => "default", teststr => teststr])

# ╔═╡ a58e383a-3837-4b4c-aa84-cf64436cd870
bse

# ╔═╡ c9a291c5-b5f5-40a6-acb3-eff4882c1516
@bind se2 Select(["a" => "✅", "b" => "🆘", "c" => "🆘"])

# ╔═╡ 9729fa52-7cff-4905-9d1c-1d0eefc8ad6e
@bind se3 Select([cos => "cosine", sin => "sine"]; default=sin)

# ╔═╡ d08b571c-fe08-4911-b9f3-5a1075be50ea
@bind se4 Select([[1,Ref(2)], sqrt, cos])

# ╔═╡ 7f05f0b5-051e-4c75-b484-944daf8a274d
se1, se2, se3, se3(123), se4

# ╔═╡ 294263fe-0986-4be1-bff5-cd9f7d261c09
bose = @bind ose1 Select(["a" => "default", teststr => teststr])

# ╔═╡ 59457dc9-edaf-40c2-8503-0c3759d85ba7
bose

# ╔═╡ a238ec69-d38b-464a-9b36-959531574d19
ose1

# ╔═╡ a95684ea-4612-45d6-b63f-41c051b53ed8
br1 = @bind r1 Radio(["a" => "default", teststr => teststr])

# ╔═╡ a5612030-0781-4cf1-b8f0-409bd3886154
br1

# ╔═╡ c2b3a7a4-8c9e-49cc-b5d0-85ad1c08fd72
r1

# ╔═╡ 69a94f6a-420a-4587-bbad-1219a390862d
push!(r1s, r1)

# ╔═╡ 998a3bd7-2d09-4b3f-8a41-50736b666dea
MultiSelect(["a" => "🆘", "b" => "✅", "c" => "🆘",  "d" => "✅", "c" => "🆘2", "c3" => "🆘"]; default=["b","d"])

# ╔═╡ 78473a2f-0a64-4aa5-a60a-94031a4167b8
bms = @bind ms1 MultiSelect(["a" => "default", teststr => teststr])

# ╔═╡ 43f86637-9f0b-480c-826a-bbf583e44646
bms

# ╔═╡ b6697df5-fd21-4553-9e90-1d33c0b51f70
ms1

# ╔═╡ 7bffc5d6-4056-4060-903e-7a1f73b6a8a0
@bind fs MultiSelect([sin, cos, tan])

# ╔═╡ 7f112de0-2678-4793-a25f-42e7495e6590
fs

# ╔═╡ 8fd52496-d4c9-4106-8a97-f19f1d8d8b0f
[f(0.5) for f in fs]

# ╔═╡ a03af14a-e030-4ac1-b61a-0275c9956454
bf = @bind f1 FilePicker()

# ╔═╡ d4a0e98d-666c-4588-8499-f253a309a403
bf

# ╔═╡ 5ed47c49-9a31-4948-8473-0311b54eb146
f1

# ╔═╡ a1666896-baf6-466c-b680-5f3e3dffff68
bd = @bind d1 DateField()

# ╔═╡ b9300522-1b92-459c-87e2-20589d36dbb5
bd

# ╔═╡ 65bdad5e-a51b-4009-8b8e-ce93286ee5e4
d1

# ╔═╡ 4f1a909d-d21a-4e60-a615-8146ba249794
@bind d2 DateField(Dates.Date(2021, 09, 20))

# ╔═╡ d52cc4d9-cdb0-46b6-a59f-5eeaa1990f20
d2

# ╔═╡ 494a163b-aed0-4e75-8ad1-c22ac46596c1
bdp1 = @bind dp1 DatePicker()

# ╔═╡ ab2bff58-f97e-4a21-b214-3266971d9fb0
dp1

# ╔═╡ fffb87ad-85a4-4d18-a5f9-cb0bcdbdaa6f
bdp2 = @bind dp2 DatePicker(Dates.Date(2022, 4, 20))

# ╔═╡ d9a04c66-9c11-4768-87c9-a66d4e1ba91c
dp2

# ╔═╡ 650f77b2-9fa5-4568-94cc-44d13b909ed5
bdp3 = @bind dp3 DatePicker(default=Dates.Date(2022, 4))

# ╔═╡ 3e4edd1c-5f4f-430a-9a8c-69417595b415
dp3

# ╔═╡ 3aefce73-f133-43e0-8680-5c17b7f90979
bti = @bind ti3 TimeField()

# ╔═╡ d128f5ac-7304-486c-8258-f05f4bd18632
ti3

# ╔═╡ 9258586a-2612-48db-be31-cf74220002d4
bti

# ╔═╡ 7a377816-30ed-4f9f-b03f-08da4548e55f
ti3

# ╔═╡ a51dc258-1e80-4cd4-9337-b9f685db244c
@bind ti2 TimeField(Dates.Time(15, 45))

# ╔═╡ 3171441c-a98b-4a5a-aedd-09ad3b445b9e
ti2

# ╔═╡ 585cff2d-df71-4901-83cd-00b4452bc9a3
btp1 = @bind tp1 TimePicker()

# ╔═╡ 80186eeb-417c-4c95-9a3d-e556bb3284a8
tp1

# ╔═╡ 83e7759c-2318-4a02-949e-f3b637f4d478
btp1

# ╔═╡ 2ab08455-80dd-4b62-b0ee-a61481d2ffb9
btp2 = @bind tp2 TimePicker(show_seconds=true)

# ╔═╡ 04403fcf-83af-44a0-84fa-64b5b3bdfdd2
tp2

# ╔═╡ f5ca10d7-c0de-41b4-95a6-384f92852074
btp3 = @bind tp3 TimePicker(Dates.Time(23,59,44))

# ╔═╡ a38a6349-5281-4fcd-9de9-45f4b06db927
tp3

# ╔═╡ ef3ccc10-efc1-4ee3-9c36-94849d29d699
btp4 = @bind tp4 TimePicker(default=Dates.Time(23,59,44), show_seconds=true)

# ╔═╡ f39d4ed3-1815-4eaa-9923-23ebf778e4e6
tp4

# ╔═╡ b123275c-48fd-4e4a-8461-4875f7c18293
bcs = @bind cs1 ColorStringPicker()

# ╔═╡ 883673fb-b8d0-49fb-ab8c-32e972894ec2
bcs

# ╔═╡ 78463563-4d1f-49f0-875f-8a30cf445a2d
cs1

# ╔═╡ 5f70cfea-0f98-428a-a01f-c3f019081869
_color_to_hex(_hex_to_color("#f0f000"))

# ╔═╡ b63f68ae-70f1-4042-ac2c-a76e09b0d686
bco = @bind co1 ColorPicker()

# ╔═╡ 2c216333-ad18-49c9-b9ec-c547d750aec6
co1

# ╔═╡ 24a2719c-c997-42d1-b884-15debc973c83
bco

# ╔═╡ 98f1d654-5629-4fea-9b7a-270ecbf46d57
md"""
You would normally use `colorant"#f0f000"` from `Colors.jl` to generate this default value.
"""

# ╔═╡ c2f4590c-8d86-408b-bc7b-1e1592aed8d3
ColorPicker(default=_hex_to_color("#f0f000"))

# ╔═╡ 524cf3d8-79f5-4b1b-8ea2-9cb9055944e1
ColorPicker(_hex_to_color("#f0f000"))

# ╔═╡ 9ade9240-1fea-4cb7-a571-a98b13cc29b2
"""
Line break without creating a new paragraph. Useful inside the `md"` macro:

# Example
```julia
md"\""
Hello \$br world!
"\""
```
"""
const br = HTML("
")

# ╔═╡ 98d251ff-67e7-4b16-b2e0-3e2102918ca2
export Slider, NumberField, Button, LabelButton, CounterButton, CheckBox, TextField, PasswordField, Select, MultiSelect, Radio, FilePicker, DateField, DatePicker, TimeField, TimePicker, ColorStringPicker, ColorPicker, br

# ╔═╡ Cell order:
# ╟─e8c5ba24-10e9-49e8-8c11-0add092637f8
# ╟─e1bbe1d7-68ef-4ee1-8174-d1ae1f822acb
# ╟─d738b448-387b-4942-af82-cc93042705a4
# ╟─81adbd39-5780-4cc6-a53f-a4472bacf1c0
# ╠═d8f907cd-2f89-4d54-a311-998dc8ee148e
# ╠═a0fb4f28-bfe4-4877-bf07-31acb9a56d2c
# ╠═ac542b84-dbc8-47e2-8835-9e43582b6ad7
# ╠═6da84fb9-a629-4e4c-819e-dd87a3e267ce
# ╠═dc3b6628-f453-46d9-b6a1-957608a20764
# ╠═a203d9d4-cd7b-4368-9f6d-e040a5757565
# ╠═98d251ff-67e7-4b16-b2e0-3e2102918ca2
# ╟─0baae341-aa0d-42fd-9f21-d40dd5a03af9
# ╠═c2b473f4-b56b-4a91-8377-6c86da895cbe
# ╠═5caa34e8-e501-4248-be65-ef9c6303d025
# ╠═46a90b45-8fef-493e-9bd1-a71d1f9c53f6
# ╠═328e9651-0ad1-46ce-904c-afd7deaacf94
# ╠═38d32393-49be-469c-840b-b58c7339a276
# ╠═75b008b2-afc0-4bd5-9183-e0e0d392a4c5
# ╠═9df251eb-b4f5-46cc-a4fe-ff2fa670b773
# ╠═7c5765ae-c10a-4677-97a3-848a423cb8b9
# ╟─d088bcdb-d851-4ad7-b5a0-751c1f348995
# ╠═ec870eea-36a4-48b6-95d7-f7c083e29856
# ╠═b44f1128-32a5-4d1d-a00b-446143074056
# ╠═f6cd1201-da84-4dee-9e88-b65fa1ff749e
# ╠═e440a357-1656-4cc4-8191-146fe82fbc8c
# ╠═629e5d68-580f-4d6b-be14-5a109091e6b7
# ╠═05f6a603-b738-47b1-b335-acaaf480a240
# ╟─e286f877-8b3c-4c74-a37c-a3458d66c1f8
# ╟─97fc914b-005f-4b4d-80cb-23016d589609
# ╟─db3aefaa-9539-4c46-ad9b-83763f9ef624
# ╟─0373d633-18bd-4936-a0ae-7a4f6f05372a
# ╟─f59eef32-4732-46db-87b0-3564433ce43e
# ╠═f7870d7f-992d-4d64-85aa-7621ab16244f
# ╠═893e22e1-a1e1-43cb-84fe-4931f3ba35c1
# ╠═7089edb6-720d-4df5-b3ca-da17d48b107e
# ╠═c32f42ee-0e7f-4648-99f7-21eff7b45cec
# ╠═efc0d77c-93d5-4634-9c0b-aa16d00ec007
# ╠═89e05f4b-c720-4ca5-a7fe-ceee0bcef9d9
# ╟─b7c21c22-17f5-44b8-98de-a261d5c7192b
# ╠═7f8e4abf-e7e7-47bc-b1cc-514fa1af106c
# ╠═c6d68308-53e7-4c60-8649-8f0161f28d70
# ╠═c111da12-d0ca-4b9b-8ede-20f4303a1c4b
# ╠═3ae2351b-ac4a-4669-bb11-39a1c029b301
# ╟─548bda96-2461-48a3-a3ad-6d113337826e
# ╠═6135dca4-86f9-4675-8a45-fa16b3d2c3eb
# ╠═cd08b524-d778-4acd-9fac-851d90df7179
# ╟─76c3b77b-08aa-4899-bbdd-4f8faa8d1486
# ╠═6cb75589-5496-4edd-9b21-ea49d5c0e733
# ╠═bcee47b1-0f45-4649-8517-0e93fa92bfe5
# ╠═73656df8-ac9f-466d-a8d0-0a2e5dbdbd8c
# ╠═e89ee9a3-5c78-4ff8-81e9-f44f5150d5f6
# ╟─f81bb386-203b-4392-b974-a1e2146b1a08
# ╠═0b46ba0f-f6ff-4df2-bd2b-aeacda9e8865
# ╠═1e522148-542a-4a2f-ad92-12421a6530dc
# ╠═1ac4abe2-5f06-42c6-b614-fb9a00e65386
# ╠═f4c5199a-e195-42ed-b398-4197b2e85aec
# ╠═1d81db28-103b-4bde-9a9a-f3038ee9b10b
# ╠═e25a2ec1-5dab-461e-bc47-6b3f1fe19d30
# ╠═be68f41c-0730-461c-8782-7e8d7a745509
# ╠═4363f31e-1d71-4ad8-bfe8-04403d2d3621
# ╠═121dc1e7-080e-48dd-9105-afa5f7886fb7
# ╠═13ed4bfd-7bfa-49dd-a212-d7f6564af8e2
# ╠═00145a3e-cb62-4c54-807b-8d2bce6a9fc9
# ╟─c9614498-54a8-4925-9353-7a13d3303916
# ╠═970681ed-1c3a-4327-b636-8cb0cdd90dbb
# ╠═e6032ca6-03a5-4bda-95d2-dcd9ee6b5924
# ╠═d4bf5249-6027-43c5-bd20-48ad95721e27
# ╠═d8c60294-0ca6-4cb0-b51d-9f6d6b370b28
# ╠═fbc6e4c1-4bd8-43a2-ac82-e6f76033fd8e
# ╟─eb4e17fd-07ba-4031-a39f-0d9fccd3d886
# ╠═57a7d0c9-2f4a-44e6-9b7a-0bbd98611c9d
# ╠═c9a291c5-b5f5-40a6-acb3-eff4882c1516
# ╠═9729fa52-7cff-4905-9d1c-1d0eefc8ad6e
# ╠═d08b571c-fe08-4911-b9f3-5a1075be50ea
# ╠═a58e383a-3837-4b4c-aa84-cf64436cd870
# ╠═7f05f0b5-051e-4c75-b484-944daf8a274d
# ╠═d64bb805-b700-4fd6-8894-2980152ce250
# ╠═4f3ba840-28ce-4790-b929-ce6af8920189
# ╟─edfdbaee-ec31-40c2-9ad5-28250fe6b651
# ╠═294263fe-0986-4be1-bff5-cd9f7d261c09
# ╠═59457dc9-edaf-40c2-8503-0c3759d85ba7
# ╠═a238ec69-d38b-464a-9b36-959531574d19
# ╠═b34d3a01-f8d6-4586-b655-5da84d586cd5
# ╠═609ab7f4-4fc4-4122-986d-9bfe54fa715d
# ╠═6459df3f-143f-4d1a-a238-4447b11cc56c
# ╠═f3bef89c-61ac-4dcf-bf47-3824f11db26f
# ╟─42e9e5ab-7d34-4300-a6c0-47f5cde658d8
# ╠═57232d88-b74f-4823-be61-8db450c93f5c
# ╠═04ed1e71-d806-423e-b99c-476ea702feb3
# ╟─7c4303a1-19be-41a2-a6c7-90146e01401d
# ╠═a95684ea-4612-45d6-b63f-41c051b53ed8
# ╠═a5612030-0781-4cf1-b8f0-409bd3886154
# ╠═c2b3a7a4-8c9e-49cc-b5d0-85ad1c08fd72
# ╠═69a94f6a-420a-4587-bbad-1219a390862d
# ╠═d9522557-07e6-4a51-ae92-3abe7a7d2732
# ╟─cc80b7eb-ca09-41ca-8015-933591378437
# ╟─38a7533e-7b0f-4c55-ade5-5a8d879d14c7
# ╟─f21db694-2acb-417d-9f4d-0d2400aa067e
# ╠═4d8ea460-ff2b-4e92-966e-89e76d4806af
# ╠═78473a2f-0a64-4aa5-a60a-94031a4167b8
# ╠═43f86637-9f0b-480c-826a-bbf583e44646
# ╠═b6697df5-fd21-4553-9e90-1d33c0b51f70
# ╠═998a3bd7-2d09-4b3f-8a41-50736b666dea
# ╠═7bffc5d6-4056-4060-903e-7a1f73b6a8a0
# ╠═7f112de0-2678-4793-a25f-42e7495e6590
# ╠═8fd52496-d4c9-4106-8a97-f19f1d8d8b0f
# ╟─e058076f-46fc-4435-ab45-530e27c95478
# ╠═a03af14a-e030-4ac1-b61a-0275c9956454
# ╠═d4a0e98d-666c-4588-8499-f253a309a403
# ╠═5ed47c49-9a31-4948-8473-0311b54eb146
# ╠═db65293b-891a-43a3-8a42-b23bf542755f
# ╟─d611e6f7-c574-4f0f-a46f-48ec8cf4b5aa
# ╠═a1666896-baf6-466c-b680-5f3e3dffff68
# ╠═b9300522-1b92-459c-87e2-20589d36dbb5
# ╠═65bdad5e-a51b-4009-8b8e-ce93286ee5e4
# ╠═4f1a909d-d21a-4e60-a615-8146ba249794
# ╠═d52cc4d9-cdb0-46b6-a59f-5eeaa1990f20
# ╟─5156eed1-04a0-4fc4-95fd-11a086a57c4a
# ╠═494a163b-aed0-4e75-8ad1-c22ac46596c1
# ╠═ab2bff58-f97e-4a21-b214-3266971d9fb0
# ╠═fffb87ad-85a4-4d18-a5f9-cb0bcdbdaa6f
# ╠═d9a04c66-9c11-4768-87c9-a66d4e1ba91c
# ╠═650f77b2-9fa5-4568-94cc-44d13b909ed5
# ╠═3e4edd1c-5f4f-430a-9a8c-69417595b415
# ╟─ea7c4d05-c516-4f07-9d48-7df9ce997939
# ╠═3aefce73-f133-43e0-8680-5c17b7f90979
# ╠═d128f5ac-7304-486c-8258-f05f4bd18632
# ╠═9258586a-2612-48db-be31-cf74220002d4
# ╠═4c9c1e24-235f-44f6-83f3-9f985f7fb536
# ╠═7a377816-30ed-4f9f-b03f-08da4548e55f
# ╠═a51dc258-1e80-4cd4-9337-b9f685db244c
# ╠═3171441c-a98b-4a5a-aedd-09ad3b445b9e
# ╟─5cff9494-55d5-4154-8a57-fb73a82e2036
# ╠═585cff2d-df71-4901-83cd-00b4452bc9a3
# ╠═80186eeb-417c-4c95-9a3d-e556bb3284a8
# ╠═83e7759c-2318-4a02-949e-f3b637f4d478
# ╠═2ab08455-80dd-4b62-b0ee-a61481d2ffb9
# ╠═04403fcf-83af-44a0-84fa-64b5b3bdfdd2
# ╠═f5ca10d7-c0de-41b4-95a6-384f92852074
# ╠═a38a6349-5281-4fcd-9de9-45f4b06db927
# ╠═ef3ccc10-efc1-4ee3-9c36-94849d29d699
# ╠═f39d4ed3-1815-4eaa-9923-23ebf778e4e6
# ╟─e9feb20c-3667-4ea9-9278-6b68ece1de6c
# ╠═b123275c-48fd-4e4a-8461-4875f7c18293
# ╠═883673fb-b8d0-49fb-ab8c-32e972894ec2
# ╠═78463563-4d1f-49f0-875f-8a30cf445a2d
# ╠═1d95c38d-d336-436d-a62e-0a3786c321ca
# ╠═724125f3-7699-4103-a5d8-bc6a00fab0ff
# ╠═632f6d08-0091-41d7-afb6-bdc7c5e4e837
# ╟─e1a67bd4-7fa9-4e15-8975-2c71e704de8c
# ╟─363236e7-fb6c-4b6f-a8a1-fef5bcadb570
# ╠═b329dcff-e69b-47d3-8b05-56562416cd89
# ╟─6eece14b-7034-4f12-a98a-d127459f3cdf
# ╠═5f70cfea-0f98-428a-a01f-c3f019081869
# ╠═2c216333-ad18-49c9-b9ec-c547d750aec6
# ╠═b63f68ae-70f1-4042-ac2c-a76e09b0d686
# ╠═24a2719c-c997-42d1-b884-15debc973c83
# ╟─98f1d654-5629-4fea-9b7a-270ecbf46d57
# ╠═c2f4590c-8d86-408b-bc7b-1e1592aed8d3
# ╠═524cf3d8-79f5-4b1b-8ea2-9cb9055944e1
# ╟─9ade9240-1fea-4cb7-a571-a98b13cc29b2


text:
abstract type Data end

struct IncludeExcludePairs{V}
    mode::Symbol
    point_ids::Vector{V}
    include_pairs::Matrix{V}
end
function IncludeExcludePairs(V)
    IncludeExcludePairs(:undef, V[], Matrix{V}(undef,0,0))
end

struct NetworkData{T,V} <: Data
    coords::Tuple{Vector{V},Vector{V},Vector{T}}
    fp::Vector{V}
    source_map::Matrix{T}
    ground_map::Matrix{T}
end

struct RasterMeta
    ncols::Int
    nrows::Int
    xllcorner::Float64
    yllcorner::Float64
    cellsize::Float64
    nodata::Float64
    transform::Array{Float64, 1}
    wkt::String
end

function RasterMeta()
    RasterMeta(0,0,0,0,0,0,[0.0],"")
end

struct RasData{T,V} <: Data
    cellmap::Matrix{T}
    polymap::Matrix{V}
    source_map::Matrix{T}
    ground_map::Matrix{T}
    points_rc::Tuple{Vector{V},Vector{V},Vector{V}}
    strengths::Matrix{T}
    included_pairs::IncludeExcludePairs{V}
    hbmeta::RasterMeta
end

function load_graph(V, gpath::String, ::Type{T}) where {T}
    g = readdlm(gpath, T)
    i = zeros(V, size(g, 1))
    j = zeros(V, size(g, 1))
    v = zeros(T, size(g, 1))
    for iter = 1:size(g, 1)
        i[iter] = g[iter,1] + 1
        j[iter] = g[iter,2] + 1
        v[iter] = g[iter,3]
    end
    i,j,v
end

read_focal_points(V, path::String) = V.(vec(readdlm(path)) .+ 1)

function read_point_strengths(T, path::String, inc = true)
    a = readdlm(path, T)
    if inc
        @. a[:,1] = a[:,1] + 1
    end
    a
end

function read_cellmap(habitat_file::String, is_res::Bool, ::Type{T}) where {T}

    cell_map, rastermeta = _grid_reader(T, habitat_file)

    gmap = similar(cell_map)
    ind = findall(x -> x == -9999, cell_map)
    if is_res
        if count(x -> x == 0, cell_map) > 0
            throw("Error: zero resistance values are not currently supported for habitat maps. Use a short-circuit region file instead.")
        else
            for i in eachindex(cell_map)
                gmap[i] = 1 ./ cell_map[i]
            end
            gmap[ind] .= 0
        end
    else
        copyto!(gmap, cell_map)
        gmap[ind] .= 0
    end
    gmap, rastermeta
end

function _grid_reader(T, file)
    endswith(file, ".gz") ? file = "/vsigzip/$(file)" : ()

    c, wkt, transform = read_raster(file, T)

    rastermeta = get_raster_meta(c, wkt, transform)

    return c, rastermeta

end

function get_raster_meta(habitat_file, wkt, transform)
    dims = size(habitat_file)
    ncols = dims[2]
    nrows = dims[1]
    xllcorner = transform[1]
    yllcorner = transform[4]  - (nrows * transform[2])
    cellsize = transform[2]
    nodata = -9999 # set in read_raster (overwrites old nodata val)
    RasterMeta(ncols, nrows, xllcorner, yllcorner, cellsize, nodata, transform, wkt)
end

function _guess_file_type(filename, f)
    hdr = readline(f)
    seek(f, 0) #TODO I think this is not necessary? -VL

    f2 = endswith(filename, "gz") ? GZip.open(filename, "r") : open(filename, "r")
    bytes = read(f2, 4)[3:4]
    close(f2)

    if bytes == [0x2a, 0x00]
        filetype = FILE_TYPE_GEOTIFF
    elseif startswith(hdr, FILE_HDR_NPY)
        filetype = FILE_TYPE_NPY
    elseif startswith(lowercase(hdr), FILE_HDR_AAGRID)
        filetype = FILE_TYPE_AAGRID
    elseif startswith(hdr, FILE_HDR_INCL_PAIRS_AAGRID)
        filetype = FILE_TYPE_INCL_PAIRS_AAGRID
    elseif startswith(hdr, FILE_HDR_INCL_PAIRS)
        filetype = FILE_TYPE_INCL_PAIRS
    else
        filetype = FILE_TYPE_TXTLIST
    end
    return filetype
end

function read_polymap(T, file::String, habitatmeta;
                      nodata_as = 0)

    polymap, rastermeta = _grid_reader(Float64, file)

    # Convert polymap to T. It's easier to do here and check for errors than
    # to add another Boolean flag argument to read_raster that specifies if the
    # file is for nodes points or polygons.
    try
        polymap = convert(Array{T}, polymap)
    catch e
        isa(e, InexactError) && @error string("Your node file (point_file in ",
                                       "the .ini) contains non-integer values.",
                                       " See the docs on specifying nodes for",
                                       " more information.")
    end

    ind = findall(x -> x == rastermeta.nodata, polymap)
    if nodata_as != -1
        polymap[ind] .= nodata_as
    end

    if rastermeta.cellsize != habitatmeta.cellsize
        cswarn("cellsize is not the same")
    elseif rastermeta.ncols != habitatmeta.ncols
        cswarn("ncols is not the same")
    elseif rastermeta.nrows != habitatmeta.nrows
        cswarn("nrows is not the same")
    elseif rastermeta.yllcorner != habitatmeta.yllcorner
        cswarn("yllcorner is not the same")
    elseif rastermeta.xllcorner != habitatmeta.xllcorner
        cswarn("xllcorner is not the same")
    end

    polymap
end

function read_point_map(V, file, habitatmeta)

    # Advanced mode
    if file == "none"
        return (V[], V[], V[])
    end

    f = endswith(file, ".gz") ? GZip.open(file, "r") : open(file, "r")
    filetype = _guess_file_type(file, f)
    _points_rc = filetype == FILE_TYPE_TXTLIST ? readdlm(file) :
                        read_polymap(V, file, habitatmeta)

    i = V[]
    j = V[]
    v = V[]
    if filetype == FILE_TYPE_TXTLIST
        I = _points_rc[:,2]
        J = _points_rc[:,3]
        v = _points_rc[:,1]
        i  = ceil.(V, habitatmeta.nrows .- (J .- habitatmeta.yllcorner) ./ habitatmeta.cellsize)
        j = ceil.(V, (I .- habitatmeta.xllcorner) ./ habitatmeta.cellsize)
    else
        _I = findall(!iszero, _points_rc)
        (i,j,v) =  (getindex.(_I, 1), getindex.(_I, 2), _points_rc[_I])
    end

    ind = findall(x -> x < 0, v)

    # Get rid of negative resistances
    for index in ind
        deleteat!(i, index)
        deleteat!(j, index)
        deleteat!(v, index)
    end

    # Sort them
    idx = sortperm(v)
    i = i[idx]
    j = j[idx]
    v = v[idx]

    if (minimum(i) < 0) || (minimum(j) < 0) ||
            (maximum(i) > (habitatmeta.nrows)) ||
            (maximum(j) > (habitatmeta.ncols))
         throw("At least one focal node location falls outside of habitat map")
    end

    if size(unique(v),1) < 2
        throw("Less than two valid focal nodes found. Please check focal node location file.")
    end

    close(f)
    V.(i), V.(j), V.(v)
end

function read_source_and_ground_maps(T, V, source_file, ground_file, habitatmeta,
                                        is_res)

    ground_map = Matrix{T}(undef,0,0)
    source_map = Matrix{T}(undef,0,0)

    f = endswith(ground_file, "gz") ? GZip.open(ground_file, "r") : open(ground_file, "r")
    filetype = _guess_file_type(ground_file, f)

    if filetype == FILE_TYPE_AAGRID || filetype == FILE_TYPE_GEOTIFF
        ground_map = read_polymap(T, ground_file, habitatmeta; nodata_as = -1)
        ground_map = map(T, ground_map)
    else
        rc = readdlm(ground_file, V)
        ground_map = -9999 * ones(T, habitatmeta.nrows, habitatmeta.ncols)
        ground_map[rc[:,2], rc[:,3]] = rc[:,1]
    end
    close(f)

    f = endswith(source_file, "gz") ? GZip.open(source_file, "r") : open(source_file, "r")
    filetype = _guess_file_type(source_file, f)

    if filetype == FILE_TYPE_AAGRID || filetype == FILE_TYPE_GEOTIFF
        source_map = read_polymap(T, source_file, habitatmeta)
        source_map = map(T, source_map)
    else
        rc = readdlm(source_file, V)
        source_map = -9999 * ones(T, habitatmeta.nrows, habitatmeta.ncols)
        source_map[rc[:,2], rc[:,3]] = rc[:,1]
    end
    close(f)

    if is_res
        ind = findall(x -> x == -9999, ground_map)
        ground_map = 1 ./ ground_map
        ground_map[ind] .= 0
    else
        ind = findall(x -> x == -9999, ground_map)
        ground_map[ind] .= 0
    end

    source_map, ground_map
end

function read_included_pairs(V, filename)

    f = endswith(filename, "gz") ? GZip.open(filename, "r") : open(filename, "r")
    filetype = _guess_file_type(filename, f)
    close(f)
    minval = 0
    maxval = 0
    mode = :undef

    if filetype == FILE_TYPE_INCL_PAIRS_AAGRID
        open(filename, "r") do f
            minval = parse(Float64, split(readline(f))[2])
            maxval = parse(Float64, split(readline(f))[2])
        end
        included_pairs = readdlm(filename, skipstart=2)
        point_ids = V.(included_pairs[:,1])
        deleteat!(point_ids, 1)
        included_pairs = included_pairs[2:end, 2:end]
        map!(x -> x > maxval ? 0 : x, included_pairs, included_pairs)
        idx = findall(x -> x >= minval, included_pairs)
        mode = :include
        bin = map(x -> x >= minval ? V(1) : V(0), included_pairs)
        return IncludeExcludePairs(mode, point_ids, bin)
    elseif filetype == FILE_TYPE_INCL_PAIRS
        open(filename, "r") do f
            mode = Symbol(split(readline(f))[2])
        end
        included_pairs = readdlm(filename, skipstart = 1)
        if size(included_pairs, 1) == 1
            pl = zeros(V, 1,2)
            pl[1,:] = included_pairs
            included_pairs = pl
        end
        point_ids = V.(sort!(unique(included_pairs)))
        idx = findall(x -> x == 0 , point_ids)
        if length(idx) > 0
            deleteat!(point_ids, idx)
            cswarn("Code to include pairs is activated, some entries did not match with focal node file. Some focal nodes may have been dropped")
        end

        mat = zeros(V, size(point_ids, 1), size(point_ids, 1))

        for i = 1:size(included_pairs, 1)
            idx1 = findfirst(x -> x == included_pairs[i, 1], point_ids)
            idx2 = findfirst(x -> x == included_pairs[i, 2], point_ids)
            if idx1 != nothing && idx2 != nothing
                mat[idx1,idx2] = 1
                mat[idx2,idx1] = 1
            end
        end

        return IncludeExcludePairs(mode, point_ids, mat)
    end

end

function get_network_data(T, V, cfg)::NetworkData{T,V}

    hab_is_res = cfg["habitat_map_is_resistances"] in TRUELIST
    hab_file = cfg["habitat_file"]
    fp_file = cfg["point_file"]
    source_file = cfg["source_file"]
    ground_file = cfg["ground_file"]

    is_pairwise = cfg["scenario"] in PAIRWISE

    i,j,v = load_graph(V, hab_file, T)
    if hab_is_res
        v = 1 ./ v
    end

    if is_pairwise
        fp = read_focal_points(V, fp_file)
    else
        fp = V[]
    end

    if !is_pairwise
        source_map = read_point_strengths(T, source_file)
        ground_map = read_point_strengths(T, ground_file)
    else
        source_map = Matrix{T}(undef,0,0)
        ground_map = Matrix{T}(undef,0,0)
    end

    NetworkData((i,j,v), fp, source_map, ground_map)
end

function load_raster_data(T, V, cfg)::RasData{T,V}

    # Habitat file
    hab_file = cfg["habitat_file"]
    hab_is_res = cfg["habitat_map_is_resistances"] in TRUELIST

    # Polygons
    use_polygons = cfg["use_polygons"] in TRUELIST
    polygon_file = cfg["polygon_file"]

    # Mask file
    use_mask = cfg["use_mask"] in TRUELIST
    mask_file = cfg["mask_file"]

    # Point file
    point_file = cfg["point_file"]

    # Variable source strengths
    use_var_source = cfg["use_variable_source_strengths"] in TRUELIST
    var_source_file = cfg["variable_source_file"]

    # Included Pairs
    use_inc_pairs = cfg["use_included_pairs"] in TRUELIST
    inc_pairs_file = cfg["included_pairs_file"]

    # Advanced mode
    is_pairwise = cfg["scenario"] in PAIRWISE
    is_advanced = cfg["scenario"] in ADVANCED
    source_file = cfg["source_file"]
    ground_file = cfg["ground_file"]
    ground_is_res = cfg["ground_file_is_resistances"] in TRUELIST

    csinfo("Reading maps", cfg["suppress_messages"] in TRUELIST)

    # Read cell map
    cellmap, hbmeta = read_cellmap(hab_file, hab_is_res, T)
    c = count(x -> x > 0, cellmap)
    csinfo("Resistance/Conductance map has $c nodes", cfg["suppress_messages"] in TRUELIST)

    # Read polymap
    if use_polygons
        polymap = read_polymap(V, polygon_file, hbmeta)
    else
        polymap = Matrix{V}(undef,0,0)
    end

    # Read and update cellmap with mask file
    if use_mask
        update!(cellmap, mask_file, hbmeta)
        sum(cellmap) == 0 && throw("Mask file deleted everything!")
    end

    # Read point file
    if !is_advanced
        points_rc = read_point_map(V, point_file, hbmeta)
    else
        points_rc = (V[], V[], V[])
    end

    # Advanced mode reading
    if is_advanced
        source_map, ground_map =
        read_source_and_ground_maps(T, V, source_file, ground_file,
                                    hbmeta, ground_is_res)
    else
        source_map, ground_map = Matrix{T}(undef,0,0), Matrix{T}(undef,0,0)
    end

    # Included Pairs
    if use_inc_pairs
        included_pairs = read_included_pairs(V, inc_pairs_file)
    else
        included_pairs = IncludeExcludePairs(V)
    end

    # Variable source strengths
    if use_var_source
        strengths = read_point_strengths(T, var_source_file)
    else
        strengths = Matrix{T}(undef, 0,0)
    end

    RasData(cellmap, polymap, source_map, ground_map, points_rc, strengths,
                    included_pairs, hbmeta)
end

function update!(cellmap::Matrix{T}, m::String, hbmeta) where {T}
    mask = read_polymap(T, m, hbmeta)
    map!(x -> x > 0 ? 1 : 0, mask, mask)
    cellmap .= cellmap .* mask
end

# Inspired by GeoArrays.read()
function read_raster(path::String, T)
    # Check if file exists (ArchGDAL error is cryptic)
    check_path = endswith(path, ".gz") ? path[10:lastindex(path)] : path
    !isfile(check_path) && error("the file \"$(check_path)\" does not exist")

    raw = ArchGDAL.unsafe_read(path)
    transform = ArchGDAL.getgeotransform(raw)
    wkt = ArchGDAL.getproj(raw)

    # Extract 1st band (should only be one band anyway)
    # to get a 2D array instead of 3D
    band = ArchGDAL.getband(raw, 1)

    # Extract the array
    array_t = ArchGDAL.read(band)

    # This handles UInt tiff rasters that can still have negative NoData values
    # Need to convert the NoData value to Int64 in these cases
    if eltype(array_t) <: Integer
        ras_type = Int64
    else
        ras_type = eltype(array_t)
    end

    # Extract no data value, first converting it to the proper type (based on
    # the raster). Then, need to convert to T. Weird, yes,
    # but it's the only way I could get it to work for all raster types... -VL
    nodata_val = convert(T, convert(ras_type, ArchGDAL.getnodatavalue(band)))

    # Transpose the array -- ArchGDAL returns a x by y array, need y by x
    array = convert(Array{T}, permutedims(array_t, [2, 1]))

    array[array .== nodata_val] .= -9999.0

    # Line to handle NaNs in datasets read from tifs
    array[isnan.(array)] .= -9999.0

    # Close connection to dataset
    ArchGDAL.destroy(raw)

    array, wkt, transform # wkt and transform are needed later for write_raster
end

ext: jl


text:
function calculate_translation_rates(state::Array{Float64,1}, data_dictionary::Dict{String,Any}, vargs...)

    # initialize -
    translation_parameters_array = Array{VLTranslationParameters,1}()
    translation_kinetics_array = Float64[]

    # get stuff from the data_dictionary/biophysical_dictionary -
    species_symbol_type_array = data_dictionary["species_symbol_type_array"]
    protein_coding_length_array = data_dictionary["protein_coding_length_array"]
    time_constant_modifier_array = data_dictionary["time_constant_modifier_array"]
    biophysical_dictionary = data_dictionary["biophysical_dictionary"]
    ribosome_concentration = biophysical_dictionary["ribosome_concentration"]
    translation_elongation_rate = biophysical_dictionary["translation_elongation_rate"]
    characteristic_initiation_time_translation = biophysical_dictionary["characteristic_initiation_time_translation"]
    KL = biophysical_dictionary["translation_saturation_constant"]
    species_symbol_type_array = biophysical_dictionary["species_symbol_type_array"]

    # which indices are protein -
    idx_protein = findall(x->x==:PROTEIN,species_symbol_type_array)

    # compute the translation parameters -
    for (index,protein_length) in enumerate(protein_coding_length_array)

        # compute kE -
        kE = translation_elongation_rate*(1/protein_length)

        # compute kI -
        kI = (1/characteristic_initiation_time_translation)

        # what is the correct index?
        correction_index = idx_protein[index]
        time_scale_parameter = time_constant_modifier_array[correction_index]

        # compute the tau factor -
        tau_factor = (kE/kI)*time_scale_parameter

        # compute the vmax -
        # use a polysome factor of 6 -
        translation_vmax = kE*ribosome_concentration

        # build -
        translationParameters = VLTranslationParameters()
        translationParameters.vmax = translation_vmax*(3600)    # convert to hr
        translationParameters.tau_factor = tau_factor
        translationParameters.KL = KL
        push!(translation_parameters_array, translationParameters)
    end

    # which indicies are mRNA?
    mRNA_index_array = findall(x->x==:MRNA,species_symbol_type_array)

    # grab the translation parameters from the data dictionary -
    number_of_translation_rates = length(translation_parameters_array)
    counter = 1
    for index = 1:number_of_translation_rates

        # use the mRNA_index_array to index into the state vector -
        mRNA_concentration = state[mRNA_index_array[index]]

        # grab the parameters struct -
        parameters_struct = translation_parameters_array[index]
        vmax = parameters_struct.vmax
        tau = parameters_struct.tau_factor
        KL = parameters_struct.KL

        # build the rate -
        value = vmax*(mRNA_concentration/(KL*tau+(1+tau)*mRNA_concentration))

        # package -
        push!(translation_kinetics_array, value)
    end

    # return -
    return translation_kinetics_array
end

max_stars_repo_name: varnerlab/CFMG

max_issues_repo_name: varnerlab/CFMG

max_forks_repo_name: varnerlab/CFMG


text:
using BinaryBuilder

# Collection of sources required to build Ogg
name = "Ogg"
version = v"1.3.3"
sources = [
    "https://downloads.xiph.org/releases/ogg/libogg-$(version).tar.gz" =>
    "c2e8a485110b97550f453226ec644ebac6cb29d1caef2902c007edab4308d985",
]

# Bash recipe for building across all platforms
script = raw"""
cd $WORKSPACE/srcdir/libogg-*/

./configure --prefix=$prefix --host=${target}
make -j${nproc}
make install
"""

# These are the platforms we will build for by default, unless further
# platforms are passed in on the command line
platforms = supported_platforms()

# The products that we will ensure are always built
products = prefix -> [
    LibraryProduct(prefix, "libogg", :libogg),
]

# Dependencies that must be installed before this package can be built
dependencies = [
]

# Build the tarballs, and possibly a `build.jl` as well.
build_tarballs(ARGS, name, version, sources, script, platforms, products, dependencies)

max_stars_repo_name: ianshmean/OggBuilder

max_issues_repo_name: ianshmean/OggBuilder

max_forks_repo_name: ianshmean/OggBuilder


text:
@cfgdef struct HGFGPT2Config <: HGFConfig
  vocab_size::Int = 50257
  n_positions::Int = 1024
  n_ctx::Int = 1024
  n_embd::Int = 768
  n_layer::Int = 12
  n_head::Int = 12
  n_inner::Union{Nothing, Int} = nothing
  activation_function::String = "gelu_new"
  resid_pdrop::Float64 = 0.1
  embd_pdrop::Float64 = 0.1
  attn_pdrop::Float64 = 0.1
  layer_norm_epsilon::Float32 = 1e-5
  initializer_range::Float32 = 0.02
  summary_type::String = "cls_index"
  summary_use_proj::Bool = true
  summary_activation::Union{Nothing, String} = nothing
  summary_proj_to_labels::Bool = true
  summary_first_dropout::Float64 = 0.1
  bos_token_id::Int = 50256
  eos_token_id::Int = 50256
end

config_type(::Val{:gpt2}) = HGFGPT2Config


text:
my_number = 1.2            # Assignment

pi                         # Pre-defined variable

My_number                  # Error - was defined without capital M

δ = 0.001                  # Unicode characters allowed (type \delta and press tab)

my_number_2 = (my_number + 1) * δ     # Use pre-defined variables in expressions

counter = 1               # counter = 1

counter += 1              # counter = counter + 1

counter ^= 4              # counter = counter ^ 4

angle = 60                # degrees

angle *= π / 180          # convert to radians

x = 3
println(2x^2 - 3x + 1)
println(1.5x^2 - .5x + 1)
println(2(x-1)^2 - 3(x-1) + 1)

println(360 / 2*π)  # Left first - equals 180*π
println(360 / 2π)   # Right first - equals 180/π

println(2^2x)       # 2^(2x), not (4)x
println(2x^2)       # 2*(x^2), not (2x)^2

#=
   Experiments with spheres
=#

radius1 = 100     # Outer sphere radius
radius2 = 50      # Inner sphere radius

# Compute the volume of each sphere
vol1 = 4π * radius1^3 / 3
vol2 = 4π * radius2^3 / 3

# Compute the volume of a hollow sphere
vol_hollow = vol1 - vol2


text:
import Base.convert

Token = Union{String, SubString{String}}

"""
LookupTable is a self-counting dictionary.
It maps words to ids and ids to words.
"""
struct LookupTable
    id2word::Dict{Int, Token}
    word2id::Dict{Token, Int}

    LookupTable() = new(Dict{Int, Token}(), Dict{Token, Int}())

    function LookupTable(tokens::Vector)
        table = LookupTable()
        @inbounds for i = 1:length(tokens)
          insert!(table, tokens[i])
        end
        return table
    end
end

function insert!(table::LookupTable, word::T) where T<:Token
    if !haskey(table.word2id, word)
      id = length(table.id2word) + 1
      table.word2id[word] = id
      table.id2word[id] = word
    end
end

getindex(table::LookupTable, word::T) where T<:Token = getindex(table.word2id, word)
getindex(table::LookupTable, id::Int) = getindex(table.id2word, id)
haskey(table::LookupTable, word::T) where T<:Token = haskey(table.word2id, word)
haskey(table::LookupTable, id::Int) = haskey(table.id2word, id)
length(table::LookupTable) = length(table.id2word)


text:
using RCall
using SigmaRidgeRegression
using LinearAlgebra
using StatsBase
using Random
using Test
using MLJ
using Plots

Random.seed!(1)
n = 200
p = 160
X = randn(n, p)
Xtable = MLJ.table(X);
βs = randn(p)./sqrt(p)
Y = X*βs .+ randn(n)
groups = GroupedFeatures([60;60;40])

_scale = true
_center = true
_param_min_ratio = 0.9
seagull_glasso = TunedSeagull(groups=groups, scale=_scale, center=_center,
                      param_min_ratio=_param_min_ratio)
seagull_mach = machine(seagull_glasso, X, Y)
fit!(seagull_mach)

glasso = GroupLassoRegressor(groups=groups,λ=0.2, scale=_scale, center=_center)
glasso_machine = machine(glasso, Xtable, Y)
fit!(glasso_machine)
λ_max =	SigmaRidgeRegression._default_hyperparameter_maximum(glasso, glasso_machine)

@test seagull_mach.report.λ_max ≈ λ_max atol=0.02

λs_opt = seagull_mach.report.best_λs

multiridge = MultiGroupRidgeRegressor(groups=groups, λs=λs_opt, scale=_scale, center=_center)
multiridge_mach = machine(multiridge, X, Y)
fit!(multiridge_mach)

@test multiridge_mach.fitresult.coef ≈ seagull_mach.fitresult.coef atol=1e-3

group_summary(groups, multiridge_mach.fitresult.coef, norm)
group_summary(groups, seagull_mach.fitresult.coef, norm)

@test MLJ.predict(seagull_mach, Xtable) ≈ MLJ.predict(multiridge_mach, Xtable) atol=0.0001


text:
using Cubature
using Distributions
using LaTeXStrings
using LinearAlgebra
using Plots

"""
Implmentation of the Monte Carlo Integrator with Adaptive Importance Sampling
from Ryu, E. and Boyd, S., Adaptive Importance Sampling via Stochastic Convex Programming,
arXiv preprint, 2015.

Assumes a functional form f : Rᵈ -> R.
Parameters:
- d: input dimension
- n_stages: number of iterations performed by the integrator
- f: pdf whose expectation should be integrated
- C: update step
- μ_glob: mean of f
"""
function ais_rb(
    d::Int,
    n_stages::Int,
    f::Function,
    C::AbstractFloat,
    μ_glob::AbstractVector,
)
    g_λ = (μ, Σ) -> MultivariateNormal(μ, Σ)
    μ = rand(d)
    Σ = I + zeros(d, d)
    S = inv(Σ)
    m = S * μ
    X_k, est = (zeros(d), zeros(d))
    error_list = Float64[]
    @inbounds for k in 1:n_stages
        mul!(μ, Σ, m)
        X_k .= rand(g_λ(μ, Σ))
        est .= (est .* (k - 1) .+ f(X_k) .* X_k / pdf(g_λ(μ, Σ), X_k)) / k
        m .-=
            C .* (norm(X_k)^2) .* (f(X_k)^2) ./ (2 * pdf(g_λ(μ, Σ), X_k) * sqrt(k)) *
            (μ - X_k)
        push!(error_list, norm(est .- μ_glob) / norm(μ_glob))
    end
    return est, error_list
end

"""
Implmentation of the Monte Carlo Integrator with Adaptive Importance Sampling
from  Oh, M-S and Berger, J., Adaptive importance sampling in Monte Carlo integration, Journal
of Statistical Computation and Simulation, 1992.

Assumes a functional form f : Rᵈ -> R.
Parameters:
- d: input dimension
- n_stages: number of iterations performed by the integrator
- f: pdf whose expectation should be integrated
- μ_glob: mean of f
- n_samples: list of number of samples used for each iteration
"""
function ais_ob(
    d::Int,
    n_stages::Int,
    f::Function,
    μ_glob::AbstractVector,
    n_samples::AbstractVector,
)
    g_λ = (λ, Σ) -> MultivariateNormal(λ, Σ)
    λ = rand(d)
    Σ = I + zeros(d, d)
    WΦ = zeros(n_stages, d + 1)
    error_list = Float64[]
    @inbounds for k in 1:n_stages
        w_k = θ -> f(θ) / pdf(g_λ(λ, Σ), θ)
        samples_k_list = [rand(g_λ(λ, Σ)) for n = 1:n_samples[k]]
        W_k = h -> sum((h.(samples_k_list)) .* (w_k.(samples_k_list)))
        WΦ[k, 1] = W_k(x -> one(eltype(x)))
        WΦ[k, 2:end] .= [W_k(x -> x[k-1]) for k = 2:d+1]
        sum_weights = sum(@view WΦ[:, 1])
        λ = [sum(@view WΦ[:, k]) ./ sum_weights for k = 2:d+1]
        push!(error_list, norm(λ - μ_d) / norm(μ_d))
    end
    return λ, error_list
end

function init_test(d::Int)
    μ_d = rand(d)
    A = rand(d, d)
    ## Trick to make Σ_d positive semidefinite
    Σ_d = A' * A
    f = x -> pdf(MvTDist(2, μ_d, Σ_d), x)
    return μ_d, Σ_d, f
end

function run_ais_rb_normal_test(d::Int, n_iters_test::Int, n_stages_rb::Int)
    error_list_rb_final = zeros(n_stages_rb)
    for i = 1:n_iters_test
        @time est, error_list = ais_rb(d, n_stages_rb, f, 0.01, μ_d)
        error_list_rb_final = error_list_rb_final .* (i - 1) / i + error_list ./ i
    end
    return error_list_rb_final
end

function run_ais_ob_normal_test(d::Int, n_iters_test::Int, n_stages::Int, n_samples_ob::AbstractVector)
    error_list_ob_final = zeros(n_stages_ob)
    for i = 1:n_iters_test
        @time est, error_list = ais_ob(d, n_stages_ob, f, μ_d, n_samples_ob)
        error_list_ob_final = error_list_ob_final .* (i - 1) / i + error_list ./ i
    end
    return error_list_ob_final
end

# Initialize integrand and test parameters
d = 2
μ_d, _, f = init_test(d)
n_iters_test = 10

# RB tests
n_stages_rb = 1000000
error_list_rb_final = run_ais_rb_normal_test(d, n_iters_test, n_stages_rb)

plot_gap = 10
plot(
    log10.([k for k = Int(n_stages_rb / 2):plot_gap:n_stages_rb]),
    log10.(error_list_rb_final[Int(n_stages_rb / 2):plot_gap:end]),
    xlabel = L"$\log(N_s)$",
    ylabel = L"$\log(\varepsilon_{rel})$",
    label = "Stochastic Convex Programming",
)

# OB tests
n_stages_ob = 1000
n_samples_ob = [Int(n_stages_ob / 2) for k = 1:n_stages_ob]
error_list_ob_final = run_ais_ob_normal_test(d, n_iters_test, n_stages_ob, n_samples_ob)

plot_gap = 10
plot(
    log10.([k for k = Int(n_stages_ob / 2):plot_gap:n_stages_ob]),
    log10.(error_list_ob_final[Int(n_stages_ob / 2):plot_gap:end]),
    xlabel = L"$\log(N_s)$",
    ylabel = L"$\log(\varepsilon_{rel})$",
    label = "Cumulative Average Parameter Update",
)


text:
module TestClntSrvr

using Test

@testset "thrift client server" begin
    ENV["TEST_SRVR_ASYNC"] = "true"
    include("srvr.jl")
    include("clnt.jl")
end

end # module TestClntSrvr


text:
export to_array, to_tuple

####
#### array aggregator
####

"""
$(TYPEDEF)

Apply `transformation` repeatedly to create an array with given `dims`.
"""
struct ArrayTransform{T <: AbstractTransform,M} <: VectorTransform
    transformation::T
    dims::NTuple{M, Int}
end

dimension(t::ArrayTransform) = dimension(t.transformation) * prod(t.dims)

"""
    as(Array, [transformation], dims...)
    as(Array, [transformation], dims)

Return a transformation that applies `transformation` (which defaults to `asℝ`, the identity
transformation for scalars) repeatedly to create an array with the given `dims`.

`Matrix` or `Vector` can be used in place of `Array`, with conforming dimensions.

# Example

```julia
as(Array, asℝ₊, 2, 3)           # transform to a 2x3 matrix of positive numbers
as(Vector, 3)                   # ℝ³ → ℝ³, identity
```
"""
as(::Type{Array}, transformation::AbstractTransform, dims::Tuple{Vararg{Int}}) =
    ArrayTransform(transformation, dims)

as(::Type{Array}, dims::Tuple{Vararg{Int}}) = as(Array, Identity(), dims)

as(::Type{Array}, transformation::AbstractTransform, dims::Int...) =
    ArrayTransform(transformation, dims)

as(::Type{Array}, dims::Int...) = as(Array, Identity(), dims)

function as(::Type{Vector}, args...)
    t = as(Array, args...)
    @argcheck length(t.dims) == 1 "Vector should have 1 dimension."
    t
end

function as(::Type{Matrix}, args...)
    t = as(Array, args...)
    @argcheck length(t.dims) == 2 "Matrix should have 2 dimensions."
    t
end

function transform_with(flag::LogJacFlag, t::ArrayTransform, x, index::T) where {T}
    @unpack transformation, dims = t
    # NOTE not using index increments as that somehow breaks type inference
    d = dimension(transformation) # length of an element transformation
    len = prod(dims)              # number of elements
    𝐼 = reshape(range(index; length = len, step = d), dims)
    yℓ = map(index -> ((y, ℓ, _) = transform_with(flag, transformation, x, index); (y, ℓ)), 𝐼)
    ℓz = logjac_zero(flag, extended_eltype(x))
    index′ = index + d * len
    first.(yℓ), isempty(yℓ) ? ℓz : ℓz + sum(last, yℓ), index′
end

function transform_with(flag::LogJacFlag, t::ArrayTransform{Identity}, x, index)
    # TODO use version below when https://github.com/FluxML/Flux.jl/issues/416 is fixed
    # y = reshape(copy(x), t.dims)
    index′ = index+dimension(t)
    y = reshape(map(identity, x[index:(index′-1)]), t.dims)
    y, logjac_zero(flag, extended_eltype(x)), index′
end

function inverse_eltype(t::ArrayTransform, x::AbstractArray)
    inverse_eltype(t.transformation, first(x)) # FIXME shortcut
end

function inverse_at!(x::AbstractVector, index, transformation_array::ArrayTransform,
                     y::AbstractArray)
    @unpack transformation, dims = transformation_array
    @argcheck size(y) == dims
    for elt in vec(y)
        index = inverse_at!(x, index, transformation, elt)
    end
    index
end

####
#### Tuple and NamedTuple aggregators
####

"""
$(SIGNATURES)

Sum of the dimension of `transformations`. Utility function, *internal*.
"""
_sum_dimensions(transformations) = reduce(+, map(dimension, transformations), init = 0)
# NOTE: See https://github.com/tpapp/TransformVariables.jl/pull/80
#       `map` and `reduce` both have specializations on `Tuple`s that make them type stable
#       even when the `Tuple` is heterogenous, but that is not currently the case with
#       `mapreduce`, therefore separate `reduce` and `map` are preferred as a workaround.

const NTransforms{N} = Tuple{Vararg{AbstractTransform,N}}

"""
$(TYPEDEF)

Transform consecutive groups of real numbers to a tuple, using the given transformations.
"""
struct TransformTuple{T} <: VectorTransform
    transformations::T
    dimension::Int
    function TransformTuple(transformations::T) where {T <: NTransforms}
        new{T}(transformations, _sum_dimensions(transformations))
    end
    function TransformTuple(transformations::T
                            ) where {N, S <: NTransforms, T <: NamedTuple{N, S}}
        new{T}(transformations, _sum_dimensions(transformations))
    end
end

dimension(tt::TransformTuple) = tt.dimension

"""
    as(tuple)
    as(namedtuple)

Return a transformation that transforms consecutive groups of real numbers to a
(named) tuple, using the given transformations.

```jldoctest
julia> t = as((asℝ₊, UnitVector(3)));

julia> dimension(t)
3

julia> transform(t, zeros(dimension(t)))
(1.0, [0.0, 0.0, 1.0])

julia> t2 = as((σ = asℝ₊, u = UnitVector(3)));

julia> dimension(t2)
3

julia> transform(t2, zeros(dimension(t2)))
(σ = 1.0, u = [0.0, 0.0, 1.0])
```
"""
as(transformations::NTransforms) = TransformTuple(transformations)

"""
$(SIGNATURES)

Helper function for transforming tuples. Used internally, to help type inference. Use via
`transfom_tuple`.
"""
_transform_tuple(flag::LogJacFlag, x::AbstractVector, index, ::Tuple{}) =
    (), logjac_zero(flag, extended_eltype(x)), index

function _transform_tuple(flag::LogJacFlag, x::AbstractVector, index, ts)
    tfirst = first(ts)
    yfirst, ℓfirst, index′ = transform_with(flag, tfirst, x, index)
    yrest, ℓrest, index′′ = _transform_tuple(flag, x, index′, Base.tail(ts))
    (yfirst, yrest...), ℓfirst + ℓrest, index′′
end

"""
$(SIGNATURES)

Helper function for tuple transformations.
"""
function transform_tuple(flag::LogJacFlag, tt::NTransforms, x, index)
    _transform_tuple(flag, x, index, tt)
end

"""
$(SIGNATURES)

Helper function determining element type of inverses from tuples. Used
internally.

*Performs no argument validation, caller should do this.*
"""
_inverse_eltype_tuple(ts::NTransforms, ys::Tuple) =
    reduce(promote_type, map(((t, y),) -> inverse_eltype(t, y), zip(ts, ys)))
# NOTE: See https://github.com/tpapp/TransformVariables.jl/pull/80
#       `map` and `reduce` both have specializations on `Tuple`s that make them type stable
#       even when the `Tuple` is heterogenous, but that is not currently the case with
#       `mapreduce`, therefore separate `reduce` and `map` are preferred as a workaround.

"""
$(SIGNATURES)

Helper function for inverting tuples of transformations. Used internally.

*Performs no argument validation, caller should do this.*
"""
function _inverse!_tuple(x::AbstractVector, index, ts::NTransforms, ys::Tuple)
    for (t, y) in zip(ts, ys)
        index = inverse_at!(x, index, t, y)
    end
    index
end

function transform_with(flag::LogJacFlag, tt::TransformTuple{<:Tuple}, x, index)
    transform_tuple(flag, tt.transformations, x, index)
end

function inverse_eltype(tt::TransformTuple{<:Tuple}, y::Tuple)
    @unpack transformations = tt
    @argcheck length(transformations) == length(y)
    _inverse_eltype_tuple(transformations, y)
end

function inverse_at!(x::AbstractVector, index, tt::TransformTuple{<:Tuple}, y::Tuple)
    @unpack transformations = tt
    @argcheck length(transformations) == length(y)
    _inverse!_tuple(x, index, tt.transformations, y)
end

as(transformations::NamedTuple{N,<:NTransforms}) where N =
    TransformTuple(transformations)

function transform_with(flag::LogJacFlag, tt::TransformTuple{<:NamedTuple}, x, index)
    @unpack transformations = tt
    y, ℓ, index′ = transform_tuple(flag, values(transformations), x, index)
    NamedTuple{keys(transformations)}(y), ℓ, index′
end

function inverse_eltype(tt::TransformTuple{<:NamedTuple}, y::NamedTuple)
    @unpack transformations = tt
    @argcheck keys(transformations) == keys(y)
    _inverse_eltype_tuple(values(transformations), values(y))
end

function inverse_at!(x::AbstractVector, index, tt::TransformTuple{<:NamedTuple}, y::NamedTuple)
    @unpack transformations = tt
    @argcheck keys(transformations) == keys(y)
    @argcheck length(x) == dimension(tt)
    _inverse!_tuple(x, index, values(transformations), values(y))
end


text:
using PolygonOps: inpolygon

function process_domain(
                        P;
                        tol = 1e-6,
                        rel = false,
                        boundary = nothing,
                       )
    randomcirc = false
    if !(P isa AbstractArray && !(eltype(P) <: Number))
        if eltype(P) <: Number
            if length(P) > 1
                w = P # vertices have been specified
            else
                if P < 0
                    randomcirc = true
                    P = -P # random circular arcs
                end
                w = @. exp(2im*pi*(1:P)/P)*(0.1+$rand(P))  # random vertices
            end
        else
            if P == :L
                w = [2.0, 2+1im, 1+1im, 1+2im, 2im, 0.0]
            elseif P == :circleL
                P = complex.([2, [2+1im, -1], 1+2im, 2im, 0.0])
            elseif P == :pent
                w = @. 0.7*exp(pi*2im*(1:5)/5)
            elseif P == :snow
                P = @. exp(2i*pi*(1:12)/12)
                w = @. P*(1+0.2*(-1)^(1:12)) / 1.4
            elseif P == :iso
                w = [1+2im, 1+3im, 2im, 1im+1, 2+1im, 2, 3+1im, 3.0+2im]
                @. w = (w-(1.5+1.5im))/1.8
            end
        end
        !(P isa AbstractArray) && (P = w)
        P = Vector{Any}(P)
        if randomcirc
            for k = 1:length(P)
                r = 0.6/rand()
                P[k] = [P[k]; (rand(Bool) ? r : -r)]
            end
        end
    end

    nw = length(P)
    w = map(first, vec(P))
    ww = similar(w, 0)                                            # bndry pts for plotting
    pt = []
    dw = zeros(nw)
    for k = 1:nw
        kn = mod(k, nw)+1                                # index of next corner
        push!(ww, w[k])
        if eltype(P[k]) <: Number
            if P[k] isa Number                             #     straight arc
                dw[k] = abs(w[kn]-w[k]);                   # distance to next corner
                push!(pt, let w=w, k=k, kn=kn
                          t -> w[k] + t*(w[kn]-w[k])/dw[k]
                      end)  # parametrization of arc
            else                                          #     circular arc
                r = P[k][2];                               # radius of arc
                a = w[k]; b = w[kn]; ab = abs(b-a);        # endpoints of arc
                theta = asin(ab/(2*r));                    # half-angle of arc
                c = a + r*exp(im*(pi/2-theta))*(b-a)/ab;   # center of arc
                dw[k] = 2*theta*r;                         # arc length of arc
                push!(pt, let r=r, theta=theta, a=a, b=b, ab=ab, c=c
                          # parametrization of arc
                          t -> c - r*exp(im*(pi/2+t/r-theta))*(b-a)/ab
                      end)
                append!(ww, pt[k].(range(0,stop=dw[k],length=50)))
            end
        else
            error("general boundary arcs not yet implemented")
        end
    end
    push!(ww, w[1])

    ## Next treat the boundary conditions
    default_bd = z -> real(z).^2
    g = Vector{Any}(undef, nw)
    for k = 1:nw
        g[k] = default_bd       # default
    end
    # This block specifies Dirichlet bndry data g.
    if boundary === nothing
    elseif boundary isa AbstractArray
        if eltype(boundary) <: Function
            g = boundary
        elseif eltype(boundary) <: Number     # if vector, convert to array of fun.
            for k = 1:nw
                g[k] = let boundary = boundary[k]
                    z -> oftype(z, boundary)
                end
            end
        else
            @goto BD_ERROR
        end
    else
        if boundary isa Function
            g = fill(boundary, nw)
        elseif boundary isa Number     # if vector, convert to array of fun.
            for k = 1:nw
                g[k] = let boundary = boundary
                    z -> oftype(z, boundary)
                end
            end
        else
            @goto BD_ERROR
        end
    end

    continuous = true         # check for disc. bndry data if 'rel' not specified
    for k = 1:nw
        j = mod(k-2,nw)+1
        gkk = g[k](w[k])
        gjk = g[j](w[k])
        if abs(gkk-gjk) > tol || isnan(gkk) || isnan(gjk)
            continuous = false   # continuity not enforced at Neumann corners
        end
    end
    if !continuous
        rel = true
    end

    return g, w, ww, pt, dw, tol, rel

    @label BD_ERROR
    error("Invalid boundary. Only use functions or numbers.")
end

function laplace(
                 P;
                 tol = 1e-6,
                 steps = false,
                 plots = true,
                 slow = false,
                 rel = false,
                 aaa = true,
                 boundary = nothing,
                 arnoldi = true,
                )
    steps && (plots = true)
    g, w, ww, pt, dw, tol, rel = process_domain(P; tol, rel, boundary)
    Zplot = ww

    # number of corners
    nw = length(w)
    wr = extrema(real(ww))
    wi = extrema(imag(ww))
    # for scale- and transl-invariance
    wc = mean(@. wr + wi * im)
    scl = max(wr[2] - wr[1], wi[2] - wi[1])
    # sets which corners get more poles
    q = 0.5
    slow && (q = 0.0)

    inpolygonc = let polygon=map(w->(real(w), imag(w)), ww)
        zs -> map(z -> inpolygon((real(z), imag(z)), polygon), zs)            # complex variant of "inpolygon"
    end

    TT = complex(float(eltype(ww)))
    outward = similar(ww, TT)
    for k = 1:nw
        forward = pt[k](0.01*dw[k]) - w[k]            # small step toward next corner
        j = mod(k-2,nw)+1
        backward = pt[j](0.99*dw[j]) - w[k]           # small step toward last corner
        tmp = im*backward*sqrt(-forward/backward);
        outward[k] = tmp/abs(tmp);                    # outward direction from corner
    end

    Nvec = Int[]
    errvec = real(TT)[]
    errk = ones(nw)
    nkv = zeros(Int, nw)
    maxstepno = 30
    err0 = Inf
    err = real(TT)

    Z = TT[]            # vector of sample points on boundary
    G = TT[]            # vector of boundary values at these points
    T = Vector{TT}[]    # vector of unit tangent vectors at these points
    pol = TT[]          # vector of poles of the rational approximation
    J = Int[]           # vector of indices of which corner each pole belongs to
    d = real(TT)[]      # vector of distances from poles to their corners
    tt = map(_->real(TT)[], 1:nw)  # array of distances of sample points along each side

    # initializations
    cc = pol
    A = Matrix{real(TT)}(undef, 0, 0)
    H = Matrix{TT}(undef, 0, 0)
    n = 0
    for stepno = 1:maxstepno
        empty!(Z); empty!(G); empty!(T); empty!(pol); empty!(J); empty!(d)
        for t in tt
            empty!(t)
        end
        # Fix poles and sample pts on bndry.  Side k means side from corner k to k+1.
        for k = 1:nw
            nk = nkv[k]                                  # no. of poles at this corner
            sk = @. sqrt(1:nk) - $sqrt(nk)
            dk = @. exp(4*sk); dk = scl*dk;                  # stronger clustering near corner
            dk = dk[@. dk > 1e-15*scl]                        # remove poles too close to corner
            polk = @. w[k] + outward[k]*dk;                  # poles near this corner
            ii = findfirst(isequal(1), inpolygonc(polk[@. dk>1e-12*scl])) # work around inaccuracy
            if ii !== nothing                               # don't allow poles in Omega
                dk = dk[1:ii-2]
                polk = polk[1:ii-2]
            end
            append!(pol, polk); append!(d, dk)
            for _ in eachindex(dk)
                push!(J, k)
            end
            dvec = [(1/3)*dk; (2/3)*dk; dk]                # finer pts for bndry sampling
            clustered = dvec[dvec.<dw[k]] # clustered pts near corner
            additional = range(0,stop=dw[k],length=max(30,nk))  # additional pts along side
            append!(tt[k], clustered)
            append!(tt[k], additional)
            j = mod(k-2,nw)+1                            # index of last corner
            append!(tt[j], dw[j].-dvec[dvec.<dw[j]])       # likewise in other direction
        end
        for k = 1:nw
            sort!(tt[k])
            tk = tt[k]; pk = pt[k]                       # abbrevations
            pktk = pk.(tk)
            append!(Z, pktk)                             # sample pts on side k
            append!(G, g[k].(pktk))                        # boundary data at these pts
            h = 1e-4                                     # 4-pt trapezoidal rule
            push!(T, @. (pk.(tk.+h)-im*pk.(tk.+im*h) - pk(tk-h)+im*pk(tk-im*h))/(4*h))    # unnormalized tangent vectors
        end
        for t in T
            @. t = t/abs(t)                                  # normalize tangent vectors
        end
        II = isnan.(G);                                   # Neumann indices
        if any(II)
            arnoldi = false
        end

        # Solve the Laplace problem
        n = 4stepno                                    # degree of polynomial term
        Np = length(pol)

        M = size(Z, 1)
        H = zeros(TT, n+1, n);                                # Arnoldi Hessenberg matrix
        if arnoldi
            Q = ones(TT, M, n+1) # cols of Q have norm sqrt(M)
            v = zeros(TT, M)
            @views for k = 1:n                                   # Arnoldi process
                @. v = (Z - wc) * Q[:, k]                        # (not yet implemented if
                for j = 1:k                                #  are Neumann BCs)
                    H[j, k] = Q[:, j]'v/M
                    @. v = v - H[j, k]*Q[:, j]
                end
                H[k+1, k] = norm(v)/sqrt(M)
                @. Q[:, k+1] = v/H[k+1, k]
            end
        else                                             # no-Arnoldi option
            Q = @. ((Z-wc)/scl)^$Transpose(0:n)                      # (for educational purposes)
        end
        A = [real(Q) imag(Q[:,2:n+1])]                  # matrix for least-sq
        if any(II)                                       # Neumann BCs, if any
            tmp = @. (0:n)*((Z[II]-wc)/scl).^[0; 0:n-1]*T[II]/scl
            A[II,:] = [imag(tmp) -real(tmp[:,2:n+1])]
        end
        # Dirichlet row entry pairs have the form Re (u+iv)*(a-ib) = [u v][a b]' = g
        # Neumann row entry pairs have the form Im (U+iV)*(a-ib) = [V -U][a b]' = 0
        # where U+iV = (u+iv)/(unit vector T in tangent direction)
        # and now u and v correspond not to d/(Z-pol) but to -d/(Z-pol)^2
        if Np > 0                                         # g linear => no poles
            complexA = @. $Transpose(d)/(Z-$Transpose(pol))
            updateA = [real(complexA) imag(complexA)]
            if isempty(A)
                A = updateA
            else
                A = [A updateA]     # columns with poles
            end
            if any(II)                                     # Neumann BCs, if any
                JJ = 2*n+1 .+ (1:2*Np);                      # column indices for poles
                A[II,1] = 0;
                tmp = @. -(d/(Z[II]-pol)^2)*T[II]
                A[II,JJ] = [imag(tmp) -real(tmp)]
            end
        end
        # corner for each col
        for _ in 1:2n+1
            push!(J, 0)
        end
        append!(J, J); append!(J, J)
        N = size(A,2)                                    # no. of cols = 2n+1+2Np
        Kj = zeros(Int, M)
        for j = 1:M
            dd = @. abs(Z[j]-w)
            Kj[j] = findfirst(isequal(minimum(dd)), dd)                   # nearest corner to Zj
        end
        if rel                                            # weights to measure error
            wt = @. abs(Z-w[Kj])/scl
        else
            wt = ones(M)
        end
        W = Diagonal(sqrt.(wt))                      # weighting for case 'rel'
        Gn = G; Gn[II] .= 0                               # set Neumann vals to 0
        c = (W*A)\(W*Gn)                                 # least-squares solution
        cc = [c[1]; c[2:n+1]-im*c[n+2:2*n+1]              # complex coeffs for f
              c[2*n+2:2*n+Np+1]-im*c[2*n+Np+2:end]]
        for k = 1:nw
            Kk = findall(isequal(k), Kj)
            errk[k] = norm(wt[Kk] .* (A[Kk,:]*c-Gn[Kk]), Inf) # error near corner k
        end
        err = norm(wt.*(A*c-Gn), Inf)                     # global error
        polmax = 100
        for k = 1:nw
            if (errk[k] > q*err) & (nkv[k] < polmax)
                nkv[k] = nkv[k]+ceil(1+sqrt(nkv[k]))      # increase no. poles
            else
                nkv[k] = max(nkv[k],ceil(stepno/2))
            end
        end
        push!(errvec, err); push!(Nvec, N)
        err < 0.5*tol && break                             # convergence success
        if err < err0                                      # save the best so far
            #TODO
        end
        if (N > 1200) || (stepno == maxstepno) || (Np >= polmax*nw)  # failure
            #TODO
            #u = u0; f = f0; Z = Z0; G = G0; A = A0; M = M0;
            #N = N0; err = err0; pol = pol0; wt = wt0;
            @warn "Loosen tolerance or add corners?"
            break
        end
    end
    f = let wc=wc, cc=cc, H=H, pol=pol, d=d, arnoldi=arnoldi, scl=scl, n=n
        z -> fzeval(z,wc,               # vector and matrix inputs
                    cc,H,pol,d,arnoldi,scl,n)    # to u and f both allowed
    end
    u = let f=f
        z -> real(f(z))
    end
    return u, err, f, Z, Zplot, A, inpolygonc
end

function fzeval(Z,wc,cc,H,pol,d,arnoldi,scl,n)
    !(Z isa Number) && (Z = vec(Z))
    ZZ = [wc; Z]
    if arnoldi
        M = length(ZZ)
        TT = eltype(H)
        Q = ones(TT, M, n+1)
        v = zeros(TT, M)
        @views for k = 1:size(H,2)
            @. v = (ZZ-wc)*Q[:,k]
            for j = 1:k
                @. v = v - H[j,k]*Q[:,j]
            end
            @. Q[:, k+1] = v/H[k+1, k]
        end
    else
        Q = @. ((ZZ-wc)/scl).^$Transpose(0:n);
    end
    if length(pol) > 0
        fZZ = [Q (@. $Transpose(d)/(ZZ-$Transpose(pol)))]*cc
    else
        fZZ = Q*cc
    end
    fZ = @. fZZ[2:end] - im*imag(fZZ[1])
    return Z isa Number ? fZ[1] : reshape(fZ, size(Z))
end


text:
immutable DRIFT{T} <: Model
    β₀::T
    β₁::T
    β₂::T
    β₃::T
    β₄::T
end

Base.show(io::IO, ::Type{DRIFT}) = print(io, "Heuristic DRIFT")

function Base.show(io::IO, m::DRIFT)
    @printf(
        io,
        "Heuristic DRIFT (β₀ = %.3f, β₁ = %.3f, β₂ = %.3f, β₃ = %.3f, β₄ = %.3f)",
        m.β₀,
        m.β₁,
        m.β₂,
        m.β₃,
        m.β₄,
    )
end

initial_parameters(::Type{DRIFT}) = Float64[0.0, 0.0, 0.0, 0.0, 0.0]

canonical_parameters(m::DRIFT) = (m.β₀, m.β₁, m.β₂, m.β₃, m.β₄)

function DRIFT(parameters::Vector)
    β₀, β₁, β₂, β₃, β₄ = (
        parameters[1],
        parameters[2],
        parameters[3],
        parameters[4],
        parameters[5],
    )
    return DRIFT(β₀, β₁, β₂, β₃, β₄)
end

@inline function predict(m::DRIFT, x2::Real, t2::Real, x1::Real, t1::Real)
    β₀, β₁, β₂, β₃, β₄ = m.β₀, m.β₁, m.β₂, m.β₃, m.β₄
    δx = x2 - x1
    δt = t2 - t1
    z = (
        β₀ +
        β₁ * δx +
        β₂ * (δx / x1) +
        β₃ * ((x2 / x1)^(1 / δt) - 1) +
        β₄ * δt
    )
    p = invlogit(z)
    return p
end

@inline function gradient_component(
    m::DRIFT,
    x2::Real,
    t2::Real,
    x1::Real,
    t1::Real,
)
    δx = x2 - x1
    δt = t2 - t1
    gr = (
        1.0,
        δx,
        (δx / x1),
        ((x2 / x1)^(1 / δt) - 1),
        δt,
    )
    return gr
end


text:
"""
    Keypoints{N}(sz) <: Block

A block representing an array of size `sz` filled with keypoints of type
`SVector{N}`.
"""
struct Keypoints{N,M} <: Block
    sz::NTuple{M,DimSize}
end
Keypoints{N}(n::Int) where {N} = Keypoints{N,1}((n,))
Keypoints{N}(sz::Tuple) where {N} = Keypoints{N,length(sz)}(sz)

function checkblock(
    b::Keypoints{N,M},
    ks::AbstractArray{<:Union{<:SVector{N},Nothing},M},
) where {M,N}
    return checksize(b.sz, size(ks))
end

mockblock(block::Keypoints{N}) where {N} = mockarray(SVector{N,Float32}, block.sz)

# ## Visualization

function showblock!(io, ::ShowText, block::Keypoints{2}, obs)
    print(io, UnicodePlots.scatterplot(first.(obs), last.(obs), marker=:cross))
end

function showblock!(io, ::ShowText, block::Bounded{2, <:Keypoints{2}}, obs)
    h, w = block.size
    plot = UnicodePlots.scatterplot(
        first.(obs), last.(obs),
        xlim=(0, w), ylim=(0, h), marker=:cross)
    print(io, plot)
end

@testset "Keypoints [block]" begin
    block = Keypoints{2}((10, 10))
    @test checkblock(block, rand(SVector{2}, 10, 10))

    block = Keypoints{2}((10, :))
    @test checkblock(block, rand(SVector{2}, 10, 10))

    ks = map(k -> (rand() > .5) ? k : nothing, rand(SVector{2}, 10, 10))
    @test checkblock(block, ks)
end


text:
cd("..")
run(`bash build.sh`)