Skip to content

Commit

Permalink
feat: no more OOP-style API
Browse files Browse the repository at this point in the history
  • Loading branch information
Massolari committed Aug 26, 2024
1 parent c6ec71c commit 49137db
Show file tree
Hide file tree
Showing 17 changed files with 196 additions and 156 deletions.
2 changes: 1 addition & 1 deletion gleam.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@ gleam_json = ">= 2.0.0 and < 3.0.0"
[dev-dependencies]
gleeunit = ">= 1.0.0 and < 2.0.0"
birdie = ">= 1.1.6 and < 2.0.0"
gleam_javascript = ">= 0.11.0 and < 1.0.0"
gleam_javascript = ">= 0.12.0 and < 1.0.0"
4 changes: 2 additions & 2 deletions manifest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ packages = [
{ name = "gleam_community_ansi", version = "1.4.0", build_tools = ["gleam"], requirements = ["gleam_community_colour", "gleam_stdlib"], otp_app = "gleam_community_ansi", source = "hex", outer_checksum = "FE79E08BF97009729259B6357EC058315B6FBB916FAD1C2FF9355115FEB0D3A4" },
{ name = "gleam_community_colour", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_community_colour", source = "hex", outer_checksum = "A49A5E3AE8B637A5ACBA80ECB9B1AFE89FD3D5351FF6410A42B84F666D40D7D5" },
{ name = "gleam_erlang", version = "0.25.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "054D571A7092D2A9727B3E5D183B7507DAB0DA41556EC9133606F09C15497373" },
{ name = "gleam_javascript", version = "0.11.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_javascript", source = "hex", outer_checksum = "483631D3001FCE8EB12ADEAD5E1B808440038E96F93DA7A32D326C82F480C0B2" },
{ name = "gleam_javascript", version = "0.12.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_javascript", source = "hex", outer_checksum = "6EB652538B31E852FE0A8307A8B6314DEB34930944B6DDC41CCC31CA344DA35D" },
{ name = "gleam_json", version = "2.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_json", source = "hex", outer_checksum = "CB10B0E7BF44282FB25162F1A24C1A025F6B93E777CCF238C4017E4EEF2CDE97" },
{ name = "gleam_stdlib", version = "0.38.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "663CF11861179AF415A625307447775C09404E752FF99A24E2057C835319F1BE" },
{ name = "gleeunit", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "F7A7228925D3EE7D0813C922E062BFD6D7E9310F0BEE585D3A42F3307E3CFD13" },
Expand All @@ -22,7 +22,7 @@ packages = [

[requirements]
birdie = { version = ">= 1.1.6 and < 2.0.0" }
gleam_javascript = { version = ">= 0.11.0 and < 1.0.0"}
gleam_javascript = { version = ">= 0.12.0 and < 1.0.0" }
gleam_json = { version = ">= 2.0.0 and < 3.0.0" }
gleam_stdlib = { version = ">= 0.34.0 and < 2.0.0" }
gleeunit = { version = ">= 1.0.0 and < 2.0.0" }
3 changes: 3 additions & 0 deletions src/app_ffi.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { useApp } from "ink";

export const exit = (app) => app.exit()
9 changes: 9 additions & 0 deletions src/focus_manager_ffi.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export const enableFocus = (manager) => manager.enableFocus()

export const disableFocus = (manager) => manager.disableFocus()

export const focusNext = (manager) => manager.focusNext()

export const focusPrevious = (manager) => manager.focusPrevious()

export const focus = (manager, id) => manager.focus(id)
26 changes: 2 additions & 24 deletions src/ink_ffi.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from "react"
import { Text, Box, Newline, Spacer, useStdin, Static, Transform, useFocus, useFocusManager } from "ink";
export { render, useInput, useApp, useStderr, useStdout } from "ink";
import { Text, Box, Newline, Spacer, Static, Transform, useFocus } from "ink";
export { render, useInput, useFocusManager } from "ink";

export function element(element, props, children) {
const parsedChildren = typeof children === "object" ? children.toArray() : [children]
Expand All @@ -14,32 +14,10 @@ export const spacer = () => React.createElement(Spacer, null, null);
export const static_ = (items, childFunc) => element(Static, { items: items.toArray() }, childFunc);
export const transform = (transform, children) => element(Transform, { transform }, children);

export const useStdin_ = () => {
const stdin = useStdin()

return {
stdin: stdin.stdin,
is_raw_mode_supported: stdin.isRawModeSupported,
set_raw_mode: stdin.setRawMode,
}
}

export const useFocus_ = (options) => {
const focus = useFocus(options)

return {
is_focused: focus.isFocused,
}
}

export const useFocusManager_ = () => {
const manager = useFocusManager()

return {
enable_focus: manager.enableFocus,
disable_focus: manager.disableFocus,
focus_next: manager.focusNext,
focus_previous: manager.focusPrevious,
focus: manager.focus,
}
}
9 changes: 9 additions & 0 deletions src/pink/app.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
pub type App

/// Get the current app instance
@external(javascript, "../app_ffi.mjs", "useApp")
pub fn get() -> App

/// Exit the app (unmount)
@external(javascript, "../app_ffi.mjs", "exit")
pub fn exit(app: App) -> Nil
35 changes: 35 additions & 0 deletions src/pink/focus_manager.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
pub type FocusManager

/// Get the Focus Manager that is able to enable or disable focus management for all components or manually switch focus to next or previous components.
@external(javascript, "../ink_ffi.mjs", "useFocusManager")
pub fn get() -> FocusManager

/// Enable focus management for all components
///
/// Note: You don't need to call this method manually, unless you've disabled focus management. Focus management is enabled by default
@external(javascript, "../focus_manager_ffi.mjs", "enableFocus")
pub fn enable_focus(manager: FocusManager) -> Nil

/// Disable focus management for all components. Currently active component (if there's one) will lose its focus.
@external(javascript, "../focus_manager_ffi.mjs", "disableFocus")
pub fn disable_focus(manager: FocusManager) -> Nil

/// Switch focus to the next focusable component.
///
/// If there's no active component right now, focus will be given to the first focusable component. If active component is the last in the list of focusable components, focus will be switched to the first active component.
///
/// Note: Ink calls this method when user presses Tab.
@external(javascript, "../focus_manager_ffi.mjs", "focusNext")
pub fn focus_next(manager: FocusManager) -> Nil

/// Switch focus to the previous focusable component.
///
/// If there's no active component right now, focus will be given to the last focusable component. If active component is the first in the list of focusable components, focus will be switched to the last active component.
///
/// Note: Ink calls this method when user presses Shift+Tab.
@external(javascript, "../focus_manager_ffi.mjs", "focusPrevious")
pub fn focus_previous(manager: FocusManager) -> Nil

/// Switch focus to the component with the specified id. If there's no component with that ID, focus will be given to the next focusable component.
@external(javascript, "../focus_manager_ffi.mjs", "focus")
pub fn focus(manager: FocusManager, id: String) -> Nil
110 changes: 2 additions & 108 deletions src/pink/hook.gleam
Original file line number Diff line number Diff line change
@@ -1,81 +1,11 @@
/// This module contains hooks that are used to interact with the terminal and handle user input.
import gleam/dynamic.{type Dynamic}
import gleam/io
import gleam/json.{type Json}
import gleam/result
import gleam/string
import pink/focus.{type Focus, type FocusOptions}
import pink/key.{type Key}

/// `App` is a record that is returned by the `app` hook.
pub type App {
App(exit: fn() -> Nil)
}

/// `Stdin` is a record that is returned by the `stdin` hook.
pub type Stdin {
Stdin(
stdin: Dynamic,
is_raw_mode_supported: Bool,
set_raw_mode: fn(Bool) -> Nil,
)
}

/// `Stdout` is a record that is returned by the `stdout` hook.
pub type Stdout {
Stdout(stdout: Dynamic, write: fn(String) -> Nil)
}

/// `Stderr` is a record that is returned by the `stderr` hook.
pub type Stderr {
Stderr(stderr: Dynamic, write: fn(String) -> Nil)
}

/// `State` is a record that is returned by the `state` hook.
pub type State(a) {
State(value: a, set: fn(a) -> Nil, set_with: fn(fn(a) -> a) -> Nil)
}

/// `FocusManager` is a record that is returned by the `focus_manager` hook.
pub type FocusManager {
FocusManager(
enable_focus: fn() -> Nil,
disable_focus: fn() -> Nil,
focus_next: fn() -> Nil,
focus_previous: fn() -> Nil,
focus: fn(String) -> Nil,
)
}

/// `app` is a hook which exposes a function to manually exit the app (unmount).
@external(javascript, "../ink_ffi.mjs", "useApp")
pub fn app() -> App

/// `stdin` is a hook which exposes stdin stream
///
/// The `Stdin` record contains the following fields:
/// - `stdin` - process.stdin. Useful if your app needs to handle user input
/// - `is_raw_mode_supported` - A boolean flag determining if the current stdin supports `set_raw_mode`. A component using `set_raw_mode` might want to use `is_raw_mode_supported` to nicely fall back in environments where raw mode is not supported
/// - `set_raw_mode` - See [setRawMode](https://nodejs.org/api/tty.html#tty_readstream_setrawmode_mode). Ink exposes this function to be able to handle Ctrl+C, that's why you should use Ink's `set_raw_mode` instead of `process.stdin.setRawMode`
@external(javascript, "../ink_ffi.mjs", "useStdin_")
pub fn stdin() -> Stdin

/// `stdout` is a hook which exposes stdout stream, where Ink renders your app.
///
/// The `Stdout` record contains the following fields:
/// - `stdout` - process.stdout
/// - `write` - Write any string to stdout, while preserving Ink's output. It's useful when you want to display some external information outside of Ink's rendering and ensure there's no conflict between the two. It's similar to `static`, except it can't accept components, it only works with strings
@external(javascript, "../ink_ffi.mjs", "useStdout")
pub fn stdout() -> Stdout

/// `stderr` is a hook which exposes stderr stream.
///
/// The `Stderr` record contains the following fields:
/// - `stderr` - process.stderr
/// - `write` - Write any string to stderr, while preserving Ink's output. It's useful when you want to display some external information outside of Ink's rendering and ensure there's no conflict between the two. It's similar to `static`, except it can't accept components, it only works with strings
@external(javascript, "../ink_ffi.mjs", "useStderr")
pub fn stderr() -> Stderr

@external(javascript, "../ink_ffi.mjs", "useInput")
fn use_input(
callback callback: fn(String, Json) -> Nil,
Expand All @@ -85,23 +15,6 @@ fn use_input(
@external(javascript, "../ink_ffi.mjs", "useFocus_")
fn use_focus(options options: Json) -> Focus

/// This hook exposes methods to enable or disable focus management for all components or manually switch focus to next or previous components.
///
/// The `FocusManager` record contains the following fields:
/// - `enable_focus` - Enable focus management for all components
/// Note: You don't need to call this method manually, unless you've disabled focus management. Focus management is enabled by default
/// - `disable_focus` - Disable focus management for all components. Currently active component (if there's one) will lose its focus.
/// - `focus_next` - Switch focus to the next focusable component. If there's no active component right now, focus will be given to the first focusable component. If active component is the last in the list of focusable components, focus will be switched to the first active component.
/// Note: Ink calls this method when user presses Tab.
/// - `focus_previous` - Switch focus to the previous focusable component. If there's no active component right now, focus will be given to the last focusable component. If active component is the first in the list of focusable components, focus will be switched to the last active component.
/// Note: Ink calls this method when user presses Shift+Tab.
/// - `focus` - Switch focus to the component with the specified id. If there's no component with that ID, focus will be given to the next focusable component.
@external(javascript, "../ink_ffi.mjs", "useFocusManager_")
pub fn focus_manager() -> FocusManager

@external(javascript, "../react_ffi.mjs", "useState")
fn use_state(initial initial: a) -> #(a, fn(fn(a) -> a) -> Nil)

/// `effect` is a hook which allows you to perform side effects in function components.
/// This is a React hook and not a Ink hook.
/// Read more about it on [react.dev docs](https://react.dev/reference/react/useEffect)
Expand Down Expand Up @@ -154,7 +67,8 @@ pub fn input(callback: fn(String, List(Key)) -> Nil, is_active: Bool) {
)
}

/// Component that uses useFocus hook becomes "focusable" to Ink, so when user presses Tab, Ink will switch focus to this component.
/// Component that uses the `focus` hook becomes "focusable" to Ink, so when user presses Tab, Ink will switch focus to this component.
///
/// If there are multiple components that execute useFocus hook, focus will be given to them in the order that these components are rendered in.
///
/// This hook returns a record with `is_focused` boolean field, which determines if this component is focused or not
Expand All @@ -163,23 +77,3 @@ pub fn focus(options: FocusOptions) {
|> focus.encode_options
|> use_focus
}

/// `state` is a hook which allows you to create a stateful value.
/// This is a React hook and not a Ink hook.
/// Read more about it on [react.dev docs](https://react.dev/reference/react/useState)
///
/// This hook is here for convenience, so you don't have to create the bindings yourself
///
/// `state` returns a record with the following fields:
/// - `value` - The current state value
/// - `set` - A function to update the state value.
/// - `set_with` - A function to update the state value. It accepts a function which receives the current state value and returns a new state value.
pub fn state(initial: a) -> State(a) {
let #(state, set_state) = use_state(initial)

State(
value: state,
set: fn(a) { set_state(fn(_) { a }) },
set_with: set_state,
)
}
23 changes: 23 additions & 0 deletions src/pink/state.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/// A reference to a state value in a React component.
pub type State(a)

/// Initialize a stateful value.
///
/// This is a React hook.
/// Read more about it on [react.dev docs](https://react.dev/reference/react/useState)
///
/// Note: This hook is here for convenience, so you don't have to create the bindings yourself
@external(javascript, "../react_ffi.mjs", "useState")
pub fn init(value value: a) -> State(a)

/// Get the current value of a state.
@external(javascript, "../react_ffi.mjs", "getState")
pub fn get(state: State(a)) -> a

/// Set the value of a state.
@external(javascript, "../react_ffi.mjs", "setState")
pub fn set(on state: State(a), value value: a) -> Nil

/// Update the value of a state using a function.
@external(javascript, "../react_ffi.mjs", "setState")
pub fn set_with(on state: State(a), with setter: fn(a) -> a) -> Nil
17 changes: 17 additions & 0 deletions src/pink/stderr.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import gleam/dynamic.{type Dynamic}

pub type Stderr

/// Get the stderr stream.
@external(javascript, "../stderr_ffi.mjs", "useStderr")
pub fn get() -> Stderr

/// process.stderr
@external(javascript, "../stderr_ffi.mjs", "stderr_")
pub fn stderr(stderr: Stderr) -> Dynamic

/// Write any string to stderr, while preserving Ink's output.
///
/// It's useful when you want to display some external information outside of Ink's rendering and ensure there's no conflict between the two. It's similar to `static`, except it can't accept components, it only works with strings
@external(javascript, "../stderr_ffi.mjs", "write")
pub fn write(on stderr: Stderr, chunk chunk: String) -> Nil
23 changes: 23 additions & 0 deletions src/pink/stdin.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import gleam/dynamic.{type Dynamic}

pub type Stdin

/// Get the stdin stream
@external(javascript, "../stdin_ffi.mjs", "useStdin")
pub fn get() -> Stdin

/// Get the process.stdin. Useful if your app needs to handle user input
@external(javascript, "../stdin_ffi.mjs", "stdin_")
pub fn stdin(stdin: Stdin) -> Dynamic

/// A boolean flag determining if the current stdin supports `set_raw_mode`.
///
/// A component using `set_raw_mode` might want to use `is_raw_mode_supported` to nicely fall back in environments where raw mode is not supported
@external(javascript, "../stdin_ffi.mjs", "isRawModeSupported")
pub fn is_raw_mode_supported(stdin: Stdin) -> Bool

/// See [setRawMode](https://nodejs.org/api/tty.html#tty_readstream_setrawmode_mode).
///
/// Ink exposes this function to be able to handle Ctrl+C, that's why you should use Ink's `set_raw_mode` instead of `process.stdin.setRawMode`
@external(javascript, "../stdin_ffi.mjs", "setRawMode")
pub fn set_raw_mode(stdin: Stdin, raw_mode: Bool) -> Nil
17 changes: 17 additions & 0 deletions src/pink/stdout.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import gleam/dynamic.{type Dynamic}

pub type Stdout

/// Get the stdout stream, where Ink renders your app.
@external(javascript, "../stdout_ffi.mjs", "useStdout")
pub fn get() -> Stdout

/// process.stdout
@external(javascript, "../stdout_ffi.mjs", "stdout_")
pub fn stdout(stdout: Stdout) -> Dynamic

/// Write any string to stdout, while preserving Ink's output.
///
/// It's useful when you want to display some external information outside of Ink's rendering and ensure there's no conflict between the two. It's similar to `static`, except it can't accept components, it only works with strings
@external(javascript, "../stdout_ffi.mjs", "write")
pub fn write(on stdout: Stdout, chunk chunk: String) -> Nil
3 changes: 3 additions & 0 deletions src/react_ffi.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,6 @@ export const fragment = (props, children) => {
return createElement(Fragment, props, ...parsedChildren)
}

export const getState = ([state, _]) => state

export const setState = ([_, setState], value) => setState(value)
5 changes: 5 additions & 0 deletions src/stderr_ffi.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export { useStderr } from "ink";

export const stderr_ = (stderr) => stderr.stderr

export const write = (stderr, chunk) => stderr.write(chunk)
7 changes: 7 additions & 0 deletions src/stdin_ffi.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export { useStdin } from "ink";

export const stdin_ = (stdin) => stdin.stdin

export const isRawModeSupported = (stdin) => stdin.isRawModeSupported

export const setRawMode = (stdin, value) => stdin.setRawMode(value)
5 changes: 5 additions & 0 deletions src/stdout_ffi.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export { useStdout } from "ink";

export const stdout_ = (stdout) => stdout.stdout

export const write = (stdout, chunk) => stdout.write(chunk)
Loading

0 comments on commit 49137db

Please sign in to comment.