import gleam/dynamic.{type Dynamic} import gleam/dynamic/decode import gleam/list import gleam/option.{type Option, None, Some} import lustre/attribute as attr import lustre/effect.{type Effect} import lustre/element.{type Element} import lustre/element/html import lustre/event pub type Model { Model(tab_key: String, control_panel_open: Bool) } pub type TabItem { TabItem(icon: String, label: String, key: String) } pub fn init(_) -> #(Model, Effect(Msg)) { #(Model(tab_key: "", control_panel_open: False), effect.none()) } pub type Msg { UserClickedControlBar UserClickedTabBox UserClickedTab(String) SomeoneToggledControlPanel(Bool) } pub fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) { case msg { UserClickedControlBar | UserClickedTabBox -> #(model, show_popover()) UserClickedTab(tab_key) -> #(Model(..model, tab_key:), show_popover()) SomeoneToggledControlPanel(control_panel_open) -> #( Model(..model, control_panel_open:), effect.none(), ) } } fn show_popover() -> Effect(Msg) { use _, root <- effect.before_paint do_show_popover(root) } @external(javascript, "./hoverbar.ffi.mjs", "showPopover") fn do_show_popover(in _root: Dynamic) -> Nil { Nil } pub fn view( model model: Model, map_msg map_msg: fn(Msg) -> msg, tabs tabs: List(TabItem), control_bar control_bar: fn(String) -> Element(msg), control_panel control_panel: fn(String) -> Option(Element(msg)), ) -> Element(msg) { html.div([attr.class("container-positioner")], [ html.div([attr.class("container")], [ tab_box(model, map_msg, tabs), view_control_bar(model, map_msg, control_bar(model.tab_key)), view_control_panel(model, map_msg, control_panel(model.tab_key)), ]), ]) } fn tab_box( model: Model, map_msg: fn(Msg) -> msg, tabs: List(TabItem), ) -> Element(msg) { html.ul( [attr.class("tab-box"), event.on_click(UserClickedTabBox |> map_msg)], tabs |> list.map(fn(tab) { html.li([], [ html.button( [ attr.type_("button"), attr.class("tab-box__button"), case model.tab_key == tab.key { True -> attr.class("tab-box__button--active") False -> attr.none() }, event.on_click(UserClickedTab(tab.key) |> map_msg), ], [html.img([attr.src(tab.icon), attr.alt(tab.label)])], ), ]) }), ) } fn view_control_bar( model: Model, map_msg: fn(Msg) -> msg, inner: Element(msg), ) -> Element(msg) { html.div( [ attr.class("control-bar"), case model.control_panel_open { True -> attr.class("control-bar--open") False -> attr.none() }, event.on_click(UserClickedControlBar |> map_msg), ], [inner], ) } fn view_control_panel( _: Model, map_msg: fn(Msg) -> msg, inner: Option(Element(msg)), ) -> Element(msg) { case inner { Some(inner_element) -> html.div([attr.class("control-panel-positioner")], [ html.div( [ attr.class("control-panel__container"), attr.popover("auto"), event.on("toggle", { use new_state <- decode.field("newState", decode.string) decode.success( SomeoneToggledControlPanel(new_state == "open") |> map_msg, ) }), ], [html.div([attr.class("control-panel")], [inner_element])], ), ]) None -> element.none() } }