diff --git a/squared_away/index.html b/squared_away/index.html index a27b965..5542a23 100644 --- a/squared_away/index.html +++ b/squared_away/index.html @@ -8,6 +8,13 @@ 🚧 form + + diff --git a/squared_away/src/squared_away.gleam b/squared_away/src/squared_away.gleam index ca5a50e..87a8e14 100644 --- a/squared_away/src/squared_away.gleam +++ b/squared_away/src/squared_away.gleam @@ -16,8 +16,6 @@ import squared_away_lang as lang import squared_away_lang/error import squared_away_lang/grid import squared_away_lang/interpreter/value -import squared_away_lang/typechecker/typ -import squared_away_lang/typechecker/type_error const initial_grid_width = 5 @@ -36,9 +34,11 @@ fn focus(id: String) -> Nil /// To start, our model will be a 5x5 grid of Strings type Model { Model( + holding_shift: Bool, grid_width: Int, grid_height: Int, display_mode: DisplayMode, + display_coords: Bool, active_cell: Option(grid.GridKey), src_grid: grid.Grid(String), value_grid: grid.Grid(Result(value.Value, error.CompileError)), @@ -49,7 +49,6 @@ type Model { type DisplayMode { DisplayValues DisplayFormulas - DisplayGridCoords } fn init(_flags) -> #(Model, effect.Effect(Msg)) { @@ -59,9 +58,11 @@ fn init(_flags) -> #(Model, effect.Effect(Msg)) { let model = Model( + holding_shift: False, grid_width: initial_grid_width, grid_height: initial_grid_height, display_mode: DisplayValues, + display_coords: False, active_cell: None, src_grid:, value_grid:, @@ -75,10 +76,12 @@ fn init(_flags) -> #(Model, effect.Effect(Msg)) { type Msg { Noop UserToggledDisplayMode(to: DisplayMode) + UserToggledDisplayCoords(to: Bool) UserSetCellValue(key: grid.GridKey, val: String) UserFocusedOnCell(key: grid.GridKey) UserFocusedOffCell UserHitKeyInCell(key: grid.GridKey, keyboard_key: String) + UserReleasedKeyInCell(keyboard_key: String) } fn update_grid(model: Model) -> Model { @@ -110,6 +113,9 @@ fn update(model: Model, msg: Msg) -> #(Model, effect.Effect(Msg)) { UserToggledDisplayMode(display_mode) -> { #(Model(..model, display_mode:), effect.none()) } + UserToggledDisplayCoords(display_coords) -> { + #(Model(..model, display_coords:), effect.none()) + } UserFocusedOnCell(key) -> { #(Model(..model, active_cell: Some(key)), effect.none()) } @@ -117,23 +123,69 @@ fn update(model: Model, msg: Msg) -> #(Model, effect.Effect(Msg)) { #(Model(..model, active_cell: None), effect.none()) } UserHitKeyInCell(key, keyboard_key) -> { - case keyboard_key { - "Enter" -> { - let new_active_cell = grid.cell_underneath(model.src_grid, key) - case new_active_cell { - Error(_) -> #(Model(..model, active_cell: None), effect.none()) - Ok(new) -> { - focus(grid.to_string(new)) - #(Model(..model, active_cell: Some(new)), effect.none()) + case keyboard_key, model.holding_shift { + "Shift", False -> #(Model(..model, holding_shift: True), effect.none()) + "ArrowUp", _ -> + set_active_cell_to(model, grid.cell_above(model.src_grid, key)) + "ArrowLeft", _ -> + set_active_cell_to(model, grid.cell_to_the_left(model.src_grid, key)) + "Enter", _ | "ArrowDown", False -> + set_active_cell_to(model, grid.cell_underneath(model.src_grid, key)) + "ArrowRight", False -> + set_active_cell_to(model, grid.cell_to_the_right(model.src_grid, key)) + "ArrowRight", True -> { + let formula = grid.get(model.src_grid, key) + let maybe_cell_to_right = grid.cell_to_the_right(model.src_grid, key) + case maybe_cell_to_right { + Error(Nil) -> #(model, effect.none()) + Ok(cell_to_right) -> { + let src_grid = grid.insert(model.src_grid, cell_to_right, formula) + let id = grid.to_string(cell_to_right) + focus(id) + let new_model = + Model(..model, src_grid:, active_cell: Some(cell_to_right)) + #(update_grid(new_model), effect.none()) + } + } + } + "ArrowDown", True -> { + let formula = grid.get(model.src_grid, key) + let maybe_cell_below = grid.cell_underneath(model.src_grid, key) + case maybe_cell_below { + Error(Nil) -> #(model, effect.none()) + Ok(cell_below) -> { + let src_grid = grid.insert(model.src_grid, cell_below, formula) + let id = grid.to_string(cell_below) + focus(id) + let new_model = + Model(..model, src_grid:, active_cell: Some(cell_below)) + #(update_grid(new_model), effect.none()) } } } + _, _ -> #(model, effect.none()) + } + } + UserReleasedKeyInCell(keyboard_key) -> { + case keyboard_key { + "Shift" -> #(Model(..model, holding_shift: False), effect.none()) _ -> #(model, effect.none()) } } } } +fn set_active_cell_to(model, key: Result(grid.GridKey, Nil)) { + case key { + Error(_) -> #(model, effect.none()) + Ok(key) -> { + let id = grid.to_string(key) + focus(id) + #(Model(..model, active_cell: Some(key)), effect.none()) + } + } +} + fn view(model: Model) -> element.Element(Msg) { let error_to_display = list.find_map(model.errors_to_display, fn(e) { @@ -150,44 +202,52 @@ fn view(model: Model) -> element.Element(Msg) { |> dict.map_values(fn(_, keys) { let cells = list.map(keys, fn(key) { - let on_enter = event.on_keydown(UserHitKeyInCell(key, _)) + let on_keydown = event.on_keydown(UserHitKeyInCell(key, _)) + let on_keyup = event.on_keyup(UserReleasedKeyInCell) let on_input = event.on_input(UserSetCellValue(key:, val: _)) let out_of_focus = event.on_blur(UserFocusedOffCell) let on_focus = event.on_focus(UserFocusedOnCell(key)) let id = attribute.id(grid.to_string(key)) - let value = case model.display_mode { - DisplayFormulas -> grid.get(model.src_grid, key) - DisplayGridCoords -> string.inspect(key) - DisplayValues -> case model.active_cell == Some(key) { - False -> case grid.get(model.value_grid, key) { + let value = + case model.display_mode, model.active_cell == Some(key) { + DisplayFormulas, _ | DisplayValues, True -> + grid.get(model.src_grid, key) + DisplayValues, _ -> + case grid.get(model.value_grid, key) { Error(e) -> error.error_type_string(e) Ok(v) -> value.value_to_string(v) } - True -> grid.get(model.src_grid, key) } - } |> attribute.value + |> attribute.value - let styles = case - list.find(model.errors_to_display, fn(i) { i.0 == key }) - { - Error(Nil) -> [] - Ok(_) -> [attribute.style([#("background-color", "red")])] + let cell_is_errored = + list.any(model.errors_to_display, fn(i) { i.0 == key }) + let error_class = case cell_is_errored { + False -> attribute.none() + True -> attribute.class("errorcell") } - let input = html.input([ - on_input, - on_focus, - out_of_focus, - value, - on_enter, - id - ]) + let input = + html.input([ + on_input, + on_focus, + out_of_focus, + value, + on_keydown, + on_keyup, + id, + attribute.type_("text"), + error_class, + ]) - html.td(styles, { - [ - input - ] - }) + case model.display_coords { + False -> html.td([], [input]) + True -> + html.td([], [ + html.label([], t(grid.to_string(key) <> ": ")), + input, + ]) + } }) html.tr([], cells) @@ -214,10 +274,9 @@ fn view(model: Model) -> element.Element(Msg) { let grid_mode_toggle = html.input([ - attribute.type_("radio"), - attribute.name("display_mode"), + attribute.type_("checkbox"), attribute.id("grid_mode"), - event.on_check(fn(_) { UserToggledDisplayMode(DisplayGridCoords) }), + event.on_check(UserToggledDisplayCoords), ]) let grid_mode_toggle_label = diff --git a/squared_away/src/squared_away_ffi.js b/squared_away/src/squared_away_ffi.js index b5e84f7..4674a74 100644 --- a/squared_away/src/squared_away_ffi.js +++ b/squared_away/src/squared_away_ffi.js @@ -1,3 +1,8 @@ export function focus(id) { - document.getElementById(id).focus() + const input = document.getElementById(id); + input.focus(); + const length = input.value.length; + setTimeout(() => { + input.setSelectionRange(length, length); + }, 0); } \ No newline at end of file diff --git a/squared_away_lang/src/squared_away_lang/grid.gleam b/squared_away_lang/src/squared_away_lang/grid.gleam index 3a30047..3a514c9 100644 --- a/squared_away_lang/src/squared_away_lang/grid.gleam +++ b/squared_away_lang/src/squared_away_lang/grid.gleam @@ -2,10 +2,9 @@ //// boilerplate around results for get operations, so I'm gonna try and extract //// them to a module -import gleam/int import gleam/dict +import gleam/int import gleam/list -import gleam/result // Making the type generic since we do a grid of src // and a grid of interpreted values @@ -18,8 +17,8 @@ pub opaque type GridKey { } pub fn to_string(key: GridKey) -> String { - let GridKey(row, col) = key - int.to_string(row) <> "_" <> int.to_string(col) + let GridKey(row, col) = key + "(" <> int.to_string(row) <> "," <> int.to_string(col) <> ")" } pub fn row(grid_key: GridKey) -> Int { @@ -80,6 +79,14 @@ pub fn cell_underneath(grid: Grid(a), key: GridKey) -> Result(GridKey, Nil) { list.find(grid.cells, fn(k) { k.row == key.row + 1 && k.col == key.col }) } +pub fn cell_above(grid: Grid(a), key: GridKey) -> Result(GridKey, Nil) { + list.find(grid.cells, fn(k) { k.row + 1 == key.row && k.col == key.col }) +} + +pub fn cell_to_the_left(grid: Grid(a), key: GridKey) -> Result(GridKey, Nil) { + list.find(grid.cells, fn(k) { k.row == key.row && k.col + 1 == key.col }) +} + pub fn intersect(row_cell: GridKey, col_cell: GridKey) -> Result(GridKey, Nil) { let GridKey(row, col_check) = row_cell let GridKey(row_check, col) = col_cell