phonograph/webc/src/viewer_controller_component.gleam

208 lines
5.9 KiB
Gleam
Raw Normal View History

import gleam/dynamic.{type Dynamic}
import gleam/dynamic/decode
import gleam/int
import gleam/io
import gleam/list
import gleam/option.{type Option, Some}
import gleam/result
import lustre.{type App}
import lustre/component
import lustre/effect.{type Effect}
import lustre/element.{type Element}
import lustre/element/html
import lustre/event
import plinth/browser/document
import plinth/browser/event as plinth_event
import context
pub const name: String = "viewer-controller"
pub fn component() -> App(Nil, Model, Msg) {
lustre.component(init, update, view, [
component.on_attribute_change("root-path", fn(value) {
ParentChangedRootPath(value) |> Ok
}),
component.on_attribute_change("n-rows", fn(value) {
int.parse(value) |> result.map(ParentChangedNRows)
}),
component.on_attribute_change("n-columns", fn(value) {
int.parse(value) |> result.map(ParentChangedNColumns)
}),
component.on_attribute_change("root-path", fn(value) {
ParentChangedRootPath(value) |> Ok
}),
])
}
pub type Model {
Model(
root_path: String,
root_path_consumers: List(Dynamic),
selected_row: Option(Int),
selected_column: Option(Int),
editing: Bool,
n_rows: Int,
n_columns: Int,
)
}
fn init(_) -> #(Model, Effect(Msg)) {
#(
Model(
root_path: "",
root_path_consumers: [],
selected_row: option.None,
selected_column: option.None,
editing: False,
n_rows: -1,
n_columns: -1,
),
effect.before_paint(fn(dispatch, _) -> Nil {
document.add_event_listener("keydown", fn(ev) -> Nil {
let key = plinth_event.key(ev)
case key {
"ArrowLeft" | "ArrowRight" | "ArrowUp" | "ArrowDown" ->
dispatch(UserPressedDirectionalKey(key))
_ -> Nil
}
})
}),
)
}
pub type Msg {
ParentChangedRootPath(String)
ParentChangedNRows(Int)
ParentChangedNColumns(Int)
ChildRequestedRootPath(Dynamic, Bool)
UserClickedCell(Int, Int)
UserPressedDirectionalKey(String)
}
fn update(model: Model, msg: Msg) -> #(Model, Effect(Msg)) {
case msg {
ParentChangedRootPath(root_path) -> #(
Model(..model, root_path:),
context.update_consumers(
model.root_path_consumers,
dynamic.string(root_path),
),
)
ParentChangedNRows(n_rows) -> #(Model(..model, n_rows:), effect.none())
ParentChangedNColumns(n_columns) -> #(
Model(..model, n_columns:),
effect.none(),
)
ChildRequestedRootPath(callback, subscribe) -> #(
case subscribe {
True ->
Model(..model, root_path_consumers: [
callback,
..model.root_path_consumers
])
False -> model
},
context.update_consumers([callback], dynamic.string(model.root_path)),
)
UserClickedCell(row, column) -> #(
Model(
..model,
selected_row: option.Some(row),
selected_column: option.Some(column),
),
move_selection(to_row: option.Some(row), to_column: Some(column)),
)
UserPressedDirectionalKey(key) -> {
case model.editing, model.selected_row, model.selected_column {
False, Some(selected_row), Some(selected_column) -> {
let first_row_selected = selected_row == 0
let last_row_selected = selected_row == model.n_rows - 1
let first_col_selected = selected_column == 0
let last_col_selected = selected_column == model.n_columns - 1
case
key,
first_row_selected,
last_row_selected,
first_col_selected,
last_col_selected
{
"ArrowLeft", _, _, False, _ -> #(
Model(..model, selected_column: option.Some(selected_column - 1)),
move_selection(
to_row: model.selected_row,
to_column: Some(selected_column - 1),
),
)
"ArrowRight", _, _, _, False -> #(
Model(..model, selected_column: option.Some(selected_column + 1)),
move_selection(
to_row: model.selected_row,
to_column: Some(selected_column + 1),
),
)
"ArrowUp", False, _, _, _ -> #(
Model(..model, selected_row: option.Some(selected_row - 1)),
move_selection(
to_row: Some(selected_row - 1),
to_column: model.selected_column,
),
)
"ArrowDown", _, False, _, _ -> #(
Model(..model, selected_row: option.Some(selected_row + 1)),
move_selection(
to_row: Some(selected_row + 1),
to_column: model.selected_column,
),
)
_, _, _, _, _ -> #(model, effect.none())
}
}
_, _, _ -> #(model, effect.none())
}
}
}
}
fn move_selection(
to_row to_row: Option(Int),
to_column to_column: Option(Int),
) -> Effect(msg) {
use _dispatch, _root <- effect.before_paint()
do_clear_selected_attrs()
case to_row, to_column {
option.Some(row), option.Some(column) -> do_set_selected_attr(row:, column:)
_, _ -> Nil
}
}
@external(javascript, "./viewer_controller_component.ffi.mjs", "clearSelectedAttrs")
fn do_clear_selected_attrs() -> Nil {
Nil
}
@external(javascript, "./viewer_controller_component.ffi.mjs", "setSelectedAttr")
fn do_set_selected_attr(row _row: Int, column _column: Int) -> Nil {
Nil
}
fn view(_: Model) -> Element(Msg) {
html.div(
[
context.on_context_request(
dynamic.string("root_path"),
ChildRequestedRootPath,
),
event.on("cell-click", {
use msg <- decode.field("detail", {
use row <- decode.field("row", decode.int)
use column <- decode.field("column", decode.int)
decode.success(UserClickedCell(row, column))
})
decode.success(msg)
}),
],
[component.default_slot([], [])],
)
}