746 lines
20 KiB
Gleam
746 lines
20 KiB
Gleam
import client/form_field.{type FormField, FormField}
|
|
import client/geofeed_form.{type GeofeedForm}
|
|
import client/geofeed_form_item.{type GeofeedFormItem, GeofeedFormItem}
|
|
import gleam/dict
|
|
import gleam/http/response.{type Response}
|
|
import gleam/int
|
|
import gleam/json
|
|
import gleam/list
|
|
import gleam/option.{type Option, None, Some}
|
|
import gleam/result
|
|
import gleam/string
|
|
import lustre
|
|
import lustre/attribute
|
|
import lustre/effect.{type Effect}
|
|
import lustre/element.{type Element}
|
|
import lustre/element/html
|
|
import lustre/element/svg
|
|
import lustre/event
|
|
import plinth/browser/document
|
|
import plinth/browser/element as plinth_element
|
|
import rsvp
|
|
import shared
|
|
import shared/cidr
|
|
import shared/city
|
|
import shared/country_code
|
|
import shared/geofeed
|
|
import shared/postcode
|
|
import shared/region_code
|
|
|
|
pub fn main() {
|
|
let geofeed =
|
|
document.query_selector("#" <> shared.model_element_id)
|
|
|> result.map(plinth_element.inner_text)
|
|
|> result.try(fn(json) {
|
|
json.parse(json, geofeed.decoder())
|
|
|> result.replace_error(Nil)
|
|
})
|
|
|> result.unwrap([])
|
|
let app = lustre.application(init, update, view)
|
|
let assert Ok(_) =
|
|
lustre.start(
|
|
app,
|
|
"#" <> shared.app_element_id,
|
|
geofeed_form.from_geofeed(geofeed),
|
|
)
|
|
Nil
|
|
}
|
|
|
|
type GeofeedSearchItem {
|
|
GeofeedSearchItem(
|
|
cidr: String,
|
|
country: String,
|
|
region: String,
|
|
city: String,
|
|
postcode: String,
|
|
)
|
|
}
|
|
|
|
type Model {
|
|
Model(
|
|
geofeed_form: GeofeedForm,
|
|
search_item: GeofeedSearchItem,
|
|
show_postcode: Bool,
|
|
saving: Bool,
|
|
errors: List(String),
|
|
)
|
|
}
|
|
|
|
fn init(geofeed_form: GeofeedForm) -> #(Model, Effect(Msg)) {
|
|
let search_item =
|
|
GeofeedSearchItem(cidr: "", country: "", region: "", city: "", postcode: "")
|
|
let model =
|
|
Model(
|
|
geofeed_form:,
|
|
search_item:,
|
|
show_postcode: False,
|
|
saving: False,
|
|
errors: [],
|
|
)
|
|
#(model, effect.none())
|
|
}
|
|
|
|
type Msg {
|
|
ServerSavedList(Result(Response(String), rsvp.Error))
|
|
UserSavedList
|
|
UserAddedItem(index: Option(Int))
|
|
UserMovedItemUp(index: Int)
|
|
UserMovedItemDown(index: Int)
|
|
UserDeletedItem(index: Int)
|
|
UserUpdatedItem(index: Int, item: GeofeedFormItem)
|
|
UserUpdatedSearchItem(search_item: GeofeedSearchItem)
|
|
UserSetShowPostcode(show_postcode: Bool)
|
|
}
|
|
|
|
fn prepend_form_field_errors(
|
|
errors errors: List(String),
|
|
form_field form_field: FormField(a),
|
|
index index: Int,
|
|
field_name field_name: String,
|
|
) -> List(String) {
|
|
case form_field.parsed_value {
|
|
Ok(_) -> errors
|
|
Error(_) -> [
|
|
"Invalid " <> field_name <> " at position " <> int.to_string(index + 1),
|
|
..errors
|
|
]
|
|
}
|
|
}
|
|
|
|
fn get_errors(model: Model) -> List(String) {
|
|
let errors =
|
|
list.index_fold(model.geofeed_form, [], fn(acc, item, index) {
|
|
acc
|
|
|> prepend_form_field_errors(
|
|
form_field: item.cidr,
|
|
index:,
|
|
field_name: "CIDR",
|
|
)
|
|
|> prepend_form_field_errors(
|
|
form_field: item.country,
|
|
index:,
|
|
field_name: "country code",
|
|
)
|
|
|> prepend_form_field_errors(
|
|
form_field: item.region,
|
|
index:,
|
|
field_name: "region code",
|
|
)
|
|
|> prepend_form_field_errors(
|
|
form_field: item.city,
|
|
index:,
|
|
field_name: "city",
|
|
)
|
|
|> prepend_form_field_errors(
|
|
form_field: item.postcode,
|
|
index:,
|
|
field_name: "postcode",
|
|
)
|
|
})
|
|
let errors =
|
|
list.index_fold(model.geofeed_form, #(errors, dict.new()), fn(acc, item, i) {
|
|
case item.cidr.parsed_value {
|
|
Ok(cidr) -> #(
|
|
case acc.1 |> dict.get(cidr) {
|
|
Ok(j) -> [
|
|
"Duplicate CIDR at position "
|
|
<> int.to_string(i + 1)
|
|
<> " (same as position "
|
|
<> int.to_string(j + 1)
|
|
<> ")",
|
|
..acc.0
|
|
]
|
|
Error(Nil) -> acc.0
|
|
},
|
|
acc.1 |> dict.insert(cidr, i),
|
|
)
|
|
Error(Nil) -> acc
|
|
}
|
|
}).0
|
|
list.reverse(errors)
|
|
}
|
|
|
|
fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) {
|
|
case msg {
|
|
ServerSavedList(Ok(_)) -> #(
|
|
Model(..model, saving: False, errors: []),
|
|
effect.none(),
|
|
)
|
|
ServerSavedList(Error(_)) -> #(
|
|
Model(..model, saving: False, errors: ["Failed to save list"]),
|
|
effect.none(),
|
|
)
|
|
UserSavedList -> {
|
|
case get_errors(model) {
|
|
[] -> #(Model(..model, saving: True), {
|
|
let url = "/api/geofeed"
|
|
let body =
|
|
model.geofeed_form |> geofeed_form.to_geofeed |> geofeed.encode
|
|
rsvp.post(url, body, rsvp.expect_ok_response(ServerSavedList))
|
|
})
|
|
errors -> #(Model(..model, errors:), effect.none())
|
|
}
|
|
}
|
|
UserAddedItem(index:) -> {
|
|
let new_item =
|
|
GeofeedFormItem(
|
|
cidr: FormField(raw_value: "", parsed_value: Error(Nil)),
|
|
country: FormField(raw_value: "", parsed_value: Ok(None)),
|
|
region: FormField(raw_value: "", parsed_value: Ok(None)),
|
|
city: FormField(raw_value: "", parsed_value: Ok(None)),
|
|
postcode: FormField(raw_value: "", parsed_value: Ok(None)),
|
|
)
|
|
let geofeed_form = case index {
|
|
Some(index) ->
|
|
list.index_fold(model.geofeed_form, [], fn(acc, item, i) {
|
|
case i == index {
|
|
True -> [item, new_item, ..acc]
|
|
False -> [item, ..acc]
|
|
}
|
|
})
|
|
|> list.reverse
|
|
None -> list.append(model.geofeed_form, [new_item])
|
|
}
|
|
#(Model(..model, geofeed_form:), effect.none())
|
|
}
|
|
UserMovedItemUp(index:) -> {
|
|
let geofeed_form =
|
|
list.index_fold(model.geofeed_form, [], fn(acc, cur, i) {
|
|
case i == index, acc {
|
|
True, [] -> [cur, ..acc]
|
|
True, [prev, ..rest] -> [prev, cur, ..rest]
|
|
False, _ -> [cur, ..acc]
|
|
}
|
|
})
|
|
|> list.reverse
|
|
#(Model(..model, geofeed_form:), effect.none())
|
|
}
|
|
UserMovedItemDown(index:) -> {
|
|
let index = list.length(model.geofeed_form) - index - 1
|
|
let geofeed_form =
|
|
list.index_fold(list.reverse(model.geofeed_form), [], fn(acc, cur, i) {
|
|
case i == index, acc {
|
|
True, [] -> [cur, ..acc]
|
|
True, [prev, ..rest] -> [prev, cur, ..rest]
|
|
False, _ -> [cur, ..acc]
|
|
}
|
|
})
|
|
#(Model(..model, geofeed_form:), effect.none())
|
|
}
|
|
UserDeletedItem(index:) -> {
|
|
let geofeed_form =
|
|
list.index_fold(model.geofeed_form, [], fn(acc, cur, i) {
|
|
case i != index {
|
|
True -> [cur, ..acc]
|
|
False -> acc
|
|
}
|
|
})
|
|
|> list.reverse
|
|
#(Model(..model, geofeed_form:), effect.none())
|
|
}
|
|
UserUpdatedItem(index:, item:) -> {
|
|
let geofeed_form =
|
|
list.index_map(model.geofeed_form, fn(cur_item, item_index) {
|
|
case item_index == index {
|
|
True -> item
|
|
False -> cur_item
|
|
}
|
|
})
|
|
#(Model(..model, geofeed_form:), effect.none())
|
|
}
|
|
UserUpdatedSearchItem(search_item:) -> {
|
|
#(Model(..model, search_item:), effect.none())
|
|
}
|
|
UserSetShowPostcode(show_postcode:) -> {
|
|
#(Model(..model, show_postcode:), effect.none())
|
|
}
|
|
}
|
|
}
|
|
|
|
const default_font_size_style = #("font-size", "0.9em")
|
|
|
|
const text_align_center_style = #("text-align", "center")
|
|
|
|
const border_style = #("border", "1px solid #8f8f9d")
|
|
|
|
const border_radius_style = #("border-radius", "0.5em")
|
|
|
|
const cursor_pointer_style = #("cursor", "pointer")
|
|
|
|
fn form_button(
|
|
on_click_msg on_click_msg: Msg,
|
|
text text: String,
|
|
) -> Element(Msg) {
|
|
html.button(
|
|
[
|
|
event.on_click(on_click_msg),
|
|
attribute.styles([
|
|
#("padding", "0.4em 1em"),
|
|
shared.font_family_style,
|
|
default_font_size_style,
|
|
border_style,
|
|
border_radius_style,
|
|
cursor_pointer_style,
|
|
]),
|
|
],
|
|
[html.text(text)],
|
|
)
|
|
}
|
|
|
|
fn view(model: Model) -> Element(Msg) {
|
|
let Model(geofeed_form:, search_item:, show_postcode:, saving:, errors:) =
|
|
model
|
|
element.fragment([
|
|
html.h1(
|
|
[
|
|
attribute.styles([
|
|
#("margin", "0.5em 0 0 0"),
|
|
text_align_center_style,
|
|
shared.font_family_style,
|
|
]),
|
|
],
|
|
[html.text("Pinpoint 📌")],
|
|
),
|
|
html.h2(
|
|
[
|
|
attribute.styles([
|
|
#("margin", "0.3em 0 0 0"),
|
|
text_align_center_style,
|
|
shared.font_family_style,
|
|
]),
|
|
],
|
|
[html.text("Geofeed Editor")],
|
|
),
|
|
html.div(
|
|
[
|
|
attribute.styles([
|
|
#("margin", "0.5em 0 0 0"),
|
|
text_align_center_style,
|
|
shared.font_family_style,
|
|
#("font-size", "1em"),
|
|
]),
|
|
],
|
|
[
|
|
html.a(
|
|
[attribute.href("https://www.iso.org/obp/ui/#iso:pub:PUB500001:en")],
|
|
[html.text("View ISO Country/Region Codes")],
|
|
),
|
|
],
|
|
),
|
|
html.div(
|
|
[attribute.styles([#("display", "flex"), #("justify-content", "center")])],
|
|
[view_geofeed_form(geofeed_form:, search_item:, show_postcode:)],
|
|
),
|
|
element.fragment(
|
|
errors
|
|
|> list.map(fn(error) {
|
|
html.div(
|
|
[
|
|
attribute.styles([
|
|
shared.error_font_color_style,
|
|
shared.font_family_style,
|
|
default_font_size_style,
|
|
text_align_center_style,
|
|
#("margin-bottom", "1em"),
|
|
]),
|
|
],
|
|
[html.text(error)],
|
|
)
|
|
}),
|
|
),
|
|
html.div(
|
|
[
|
|
attribute.styles([
|
|
#("margin-bottom", "1em"),
|
|
#("display", "flex"),
|
|
#("justify-content", "center"),
|
|
#("gap", "0.6em"),
|
|
]),
|
|
],
|
|
[
|
|
form_button(on_click_msg: UserAddedItem(None), text: "Add"),
|
|
form_button(on_click_msg: UserSavedList, text: case saving {
|
|
True -> "Saving..."
|
|
False -> "Save"
|
|
}),
|
|
html.label(
|
|
[
|
|
attribute.styles([
|
|
#("padding", "0.4em 1em 0.4em 0.7em"),
|
|
shared.font_family_style,
|
|
default_font_size_style,
|
|
border_style,
|
|
border_radius_style,
|
|
]),
|
|
],
|
|
[
|
|
html.input([
|
|
attribute.type_("checkbox"),
|
|
event.on_check(UserSetShowPostcode),
|
|
]),
|
|
html.text("Postcode"),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
])
|
|
}
|
|
|
|
fn input(
|
|
value value: String,
|
|
is_valid is_valid: Bool,
|
|
chars chars: Int,
|
|
set_max_length set_max_length: Bool,
|
|
placeholder placeholder: String,
|
|
on_input_handler on_input_handler: fn(String) -> Msg,
|
|
) {
|
|
html.input([
|
|
attribute.styles([
|
|
#("padding", "0.4em 0.6em"),
|
|
#("width", int.to_string(chars) <> "ch"),
|
|
shared.font_family_style,
|
|
default_font_size_style,
|
|
border_style,
|
|
border_radius_style,
|
|
..case is_valid {
|
|
True -> []
|
|
False -> [shared.error_font_color_style]
|
|
}
|
|
]),
|
|
attribute.placeholder(placeholder),
|
|
attribute.value(value),
|
|
event.on_input(on_input_handler),
|
|
..case set_max_length {
|
|
True -> [attribute.maxlength(chars)]
|
|
False -> []
|
|
}
|
|
])
|
|
}
|
|
|
|
fn form_field_input(
|
|
form_field form_field: FormField(a),
|
|
chars chars: Int,
|
|
set_max_length set_max_length: Bool,
|
|
placeholder placeholder: String,
|
|
on_input_handler on_input_handler: fn(String) -> Msg,
|
|
) {
|
|
input(
|
|
value: form_field.raw_value,
|
|
is_valid: result.is_ok(form_field.parsed_value),
|
|
chars:,
|
|
set_max_length:,
|
|
placeholder:,
|
|
on_input_handler:,
|
|
)
|
|
}
|
|
|
|
fn field_search(
|
|
value value: String,
|
|
form_field form_field: FormField(a),
|
|
) -> Bool {
|
|
value == ""
|
|
|| string.contains(
|
|
does: string.lowercase(form_field.raw_value),
|
|
contain: string.lowercase(value),
|
|
)
|
|
}
|
|
|
|
fn item_search(
|
|
search_item search_item: GeofeedSearchItem,
|
|
item item: GeofeedFormItem,
|
|
) -> Bool {
|
|
field_search(value: search_item.cidr, form_field: item.cidr)
|
|
&& field_search(value: search_item.country, form_field: item.country)
|
|
&& field_search(value: search_item.region, form_field: item.region)
|
|
&& field_search(value: search_item.city, form_field: item.city)
|
|
&& field_search(value: search_item.postcode, form_field: item.postcode)
|
|
}
|
|
|
|
fn view_geofeed_form(
|
|
geofeed_form geofeed_form: GeofeedForm,
|
|
search_item search_item: GeofeedSearchItem,
|
|
show_postcode show_postcode: Bool,
|
|
) -> Element(Msg) {
|
|
case geofeed_form {
|
|
[] ->
|
|
html.p([attribute.styles([text_align_center_style])], [
|
|
html.text("No items in your geofeed yet."),
|
|
])
|
|
_ -> {
|
|
let total = list.length(geofeed_form)
|
|
html.div([], [
|
|
html.div(
|
|
[
|
|
attribute.styles([
|
|
#("margin", "1em 0"),
|
|
#("display", "flex"),
|
|
#("gap", "0.6em"),
|
|
]),
|
|
],
|
|
[
|
|
input(
|
|
value: search_item.cidr,
|
|
is_valid: True,
|
|
chars: 43,
|
|
set_max_length: True,
|
|
placeholder: "CIDR",
|
|
on_input_handler: fn(value) {
|
|
UserUpdatedSearchItem(
|
|
GeofeedSearchItem(
|
|
..search_item,
|
|
cidr: string.lowercase(value),
|
|
),
|
|
)
|
|
},
|
|
),
|
|
input(
|
|
value: search_item.country,
|
|
is_valid: True,
|
|
chars: 2,
|
|
set_max_length: True,
|
|
placeholder: "CC",
|
|
on_input_handler: fn(value) {
|
|
UserUpdatedSearchItem(
|
|
GeofeedSearchItem(
|
|
..search_item,
|
|
country: string.uppercase(value),
|
|
),
|
|
)
|
|
},
|
|
),
|
|
input(
|
|
value: search_item.region,
|
|
is_valid: True,
|
|
chars: 6,
|
|
set_max_length: True,
|
|
placeholder: "Region",
|
|
on_input_handler: fn(value) {
|
|
UserUpdatedSearchItem(
|
|
GeofeedSearchItem(
|
|
..search_item,
|
|
region: string.uppercase(value),
|
|
),
|
|
)
|
|
},
|
|
),
|
|
input(
|
|
value: search_item.city,
|
|
is_valid: True,
|
|
chars: 24,
|
|
set_max_length: False,
|
|
placeholder: "City",
|
|
on_input_handler: fn(value) {
|
|
UserUpdatedSearchItem(
|
|
GeofeedSearchItem(..search_item, city: value),
|
|
)
|
|
},
|
|
),
|
|
..case show_postcode {
|
|
True -> [
|
|
input(
|
|
value: search_item.postcode,
|
|
is_valid: True,
|
|
chars: 10,
|
|
set_max_length: False,
|
|
placeholder: "Postcode",
|
|
on_input_handler: fn(value) {
|
|
UserUpdatedSearchItem(
|
|
GeofeedSearchItem(..search_item, postcode: value),
|
|
)
|
|
},
|
|
),
|
|
]
|
|
False -> []
|
|
}
|
|
],
|
|
),
|
|
case list.any(geofeed_form, item_search(search_item:, item: _)) {
|
|
True ->
|
|
html.ol(
|
|
[attribute.styles([#("margin", "0"), #("padding", "0")])],
|
|
list.index_map(geofeed_form, fn(item, index) {
|
|
html.li(
|
|
[
|
|
attribute.value(int.to_string(index + 1)),
|
|
..case item_search(search_item:, item:) {
|
|
True -> []
|
|
False -> [attribute.styles([#("display", "none")])]
|
|
}
|
|
],
|
|
[view_geofeed_item(item:, index:, total:, show_postcode:)],
|
|
)
|
|
}),
|
|
)
|
|
False ->
|
|
html.p([attribute.styles([text_align_center_style])], [
|
|
html.text("Search returned no results."),
|
|
])
|
|
},
|
|
])
|
|
}
|
|
}
|
|
}
|
|
|
|
fn item_button(
|
|
on_click_msg on_click_msg: Msg,
|
|
disabled disabled: Bool,
|
|
icon_path icon_path: String,
|
|
) -> Element(Msg) {
|
|
html.button(
|
|
[
|
|
event.on_click(on_click_msg),
|
|
attribute.disabled(disabled),
|
|
attribute.styles([
|
|
#("padding", "0 0.3em"),
|
|
#("display", "inline-flex"),
|
|
#("align-items", "center"),
|
|
shared.font_family_style,
|
|
#("font-size", "1.2em"),
|
|
border_style,
|
|
border_radius_style,
|
|
..case disabled {
|
|
True -> []
|
|
False -> [cursor_pointer_style]
|
|
}
|
|
]),
|
|
],
|
|
[
|
|
html.svg(
|
|
[
|
|
attribute.attribute("viewBox", "0 0 512 512"),
|
|
attribute.attribute("fill", "white"),
|
|
attribute.styles([#("width", "1em")]),
|
|
],
|
|
[svg.path([attribute.attribute("d", icon_path)])],
|
|
),
|
|
],
|
|
)
|
|
}
|
|
|
|
fn view_geofeed_item(
|
|
item item: GeofeedFormItem,
|
|
index index: Int,
|
|
total total: Int,
|
|
show_postcode show_postcode: Bool,
|
|
) -> Element(Msg) {
|
|
let item_buttons = [
|
|
item_button(
|
|
on_click_msg: UserMovedItemUp(index:),
|
|
disabled: index == 0,
|
|
icon_path: "m233.4 137.4c12.5-12.5 32.8-12.5 45.3 0l160 160c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0l-137.4-137.4-137.4 137.3c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3l160-160z",
|
|
),
|
|
item_button(
|
|
on_click_msg: UserMovedItemDown(index:),
|
|
disabled: index == total - 1,
|
|
icon_path: "m233.4 374.6c12.5 12.5 32.8 12.5 45.3 0l160-160c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-137.4 137.4-137.4-137.3c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l160 160z",
|
|
),
|
|
item_button(
|
|
on_click_msg: UserAddedItem(index: Some(index)),
|
|
disabled: False,
|
|
icon_path: "m288 80c0-17.7-14.3-32-32-32s-32 14.3-32 32l0 144-144 0c-17.7 0-32 14.3-32 32s14.3 32 32 32l144 0 0 144c0 17.7 14.3 32 32 32s32-14.3 32-32l0-144 144 0c17.7 0 32-14.3 32-32s-14.3-32-32-32l-144 0 0-144z",
|
|
),
|
|
item_button(
|
|
on_click_msg: UserDeletedItem(index:),
|
|
disabled: False,
|
|
icon_path: "m406.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-105.3 105.4-105.4-105.3c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l105.4 105.3-105.3 105.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0l105.3-105.4 105.4 105.3c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3l-105.4-105.3 105.3-105.4z",
|
|
),
|
|
]
|
|
html.div(
|
|
[
|
|
attribute.styles([
|
|
#("margin", "1em 0"),
|
|
#("display", "flex"),
|
|
#("gap", "0.6em"),
|
|
]),
|
|
],
|
|
[
|
|
form_field_input(
|
|
form_field: item.cidr,
|
|
chars: 43,
|
|
set_max_length: True,
|
|
placeholder: "CIDR",
|
|
on_input_handler: fn(value) {
|
|
UserUpdatedItem(
|
|
index,
|
|
GeofeedFormItem(
|
|
..item,
|
|
cidr: form_field.parse(string.lowercase(value), cidr.parse),
|
|
),
|
|
)
|
|
},
|
|
),
|
|
form_field_input(
|
|
form_field: item.country,
|
|
chars: 2,
|
|
set_max_length: True,
|
|
placeholder: "CC",
|
|
on_input_handler: fn(value) {
|
|
UserUpdatedItem(
|
|
index,
|
|
GeofeedFormItem(
|
|
..item,
|
|
country: form_field.parse_optional(
|
|
string.uppercase(value),
|
|
country_code.parse,
|
|
),
|
|
),
|
|
)
|
|
},
|
|
),
|
|
form_field_input(
|
|
form_field: item.region,
|
|
chars: 6,
|
|
set_max_length: True,
|
|
placeholder: "Region",
|
|
on_input_handler: fn(value) {
|
|
UserUpdatedItem(
|
|
index,
|
|
GeofeedFormItem(
|
|
..item,
|
|
region: form_field.parse_optional(
|
|
string.uppercase(value),
|
|
region_code.parse,
|
|
),
|
|
),
|
|
)
|
|
},
|
|
),
|
|
form_field_input(
|
|
form_field: item.city,
|
|
chars: 24,
|
|
set_max_length: False,
|
|
placeholder: "City",
|
|
on_input_handler: fn(value) {
|
|
UserUpdatedItem(
|
|
index,
|
|
GeofeedFormItem(
|
|
..item,
|
|
city: form_field.parse_optional(value, city.parse),
|
|
),
|
|
)
|
|
},
|
|
),
|
|
..case show_postcode {
|
|
True -> [
|
|
form_field_input(
|
|
form_field: item.postcode,
|
|
chars: 10,
|
|
set_max_length: False,
|
|
placeholder: "Postcode",
|
|
on_input_handler: fn(value) {
|
|
UserUpdatedItem(
|
|
index,
|
|
GeofeedFormItem(
|
|
..item,
|
|
postcode: form_field.parse_optional(value, postcode.parse),
|
|
),
|
|
)
|
|
},
|
|
),
|
|
..item_buttons
|
|
]
|
|
False -> item_buttons
|
|
}
|
|
],
|
|
)
|
|
}
|