├── example ├── .tool-versions ├── .gitignore ├── test │ └── example_test.gleam ├── .github │ └── workflows │ │ └── test.yml ├── README.md ├── gleam.toml ├── index.html ├── src │ └── example.gleam └── manifest.toml ├── .gitignore ├── src ├── lustre_virtual_list_ffi.mjs └── lustre_virtual_list.gleam ├── test └── lustre_virtual_list_test.gleam ├── .github └── workflows │ └── test.yml ├── gleam.toml ├── manifest.toml ├── README.md └── LICENSE.md /example/.tool-versions: -------------------------------------------------------------------------------- 1 | erlang 26.2.4 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.beam 2 | *.ez 3 | build 4 | erl_crash.dump 5 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | *.beam 2 | *.ez 3 | build 4 | erl_crash.dump 5 | priv 6 | -------------------------------------------------------------------------------- /src/lustre_virtual_list_ffi.mjs: -------------------------------------------------------------------------------- 1 | export function getEventScrollY(event) { 2 | return event.target.scrollTop 3 | } 4 | -------------------------------------------------------------------------------- /example/test/example_test.gleam: -------------------------------------------------------------------------------- 1 | import gleeunit 2 | import gleeunit/should 3 | 4 | pub fn main() { 5 | gleeunit.main() 6 | } 7 | 8 | // gleeunit test functions end in `_test` 9 | pub fn hello_world_test() { 10 | 1 11 | |> should.equal(1) 12 | } 13 | -------------------------------------------------------------------------------- /test/lustre_virtual_list_test.gleam: -------------------------------------------------------------------------------- 1 | import gleeunit 2 | import gleeunit/should 3 | 4 | pub fn main() { 5 | gleeunit.main() 6 | } 7 | 8 | // gleeunit test functions end in `_test` 9 | pub fn hello_world_test() { 10 | 1 11 | |> should.equal(1) 12 | } 13 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - main 8 | pull_request: 9 | 10 | jobs: 11 | test: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | - uses: erlef/setup-beam@v1 16 | with: 17 | otp-version: "27.1.2" 18 | gleam-version: "1.5.1" 19 | rebar3-version: "3" 20 | # elixir-version: "1.15.4" 21 | - run: gleam format --check src test 22 | - run: gleam deps download 23 | - run: gleam test 24 | -------------------------------------------------------------------------------- /example/.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - main 8 | pull_request: 9 | 10 | jobs: 11 | test: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | - uses: erlef/setup-beam@v1 16 | with: 17 | otp-version: "27.1.2" 18 | gleam-version: "1.5.1" 19 | rebar3-version: "3" 20 | # elixir-version: "1.15.4" 21 | - run: gleam format --check src test 22 | - run: gleam deps download 23 | - run: gleam test 24 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # example 2 | 3 | [![Package Version](https://img.shields.io/hexpm/v/example)](https://hex.pm/packages/example) 4 | [![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/example/) 5 | 6 | ## Quick start 7 | 8 | ```sh 9 | gleam run # Run the project 10 | gleam test # Run the tests 11 | gleam shell # Run an Erlang shell 12 | ``` 13 | 14 | ## Installation 15 | 16 | If available on Hex this package can be added to your Gleam project: 17 | 18 | ```sh 19 | gleam add example 20 | ``` 21 | 22 | and its documentation can be found at . 23 | -------------------------------------------------------------------------------- /gleam.toml: -------------------------------------------------------------------------------- 1 | name = "lustre_virtual_list" 2 | version = "1.0.0" 3 | 4 | # Fill out these fields if you intend to generate HTML documentation or publish 5 | # your project to the Hex package manager. 6 | # 7 | description = "A virtual list component for lustre." 8 | licences = ["Apache-2.0"] 9 | repository = { type = "github", user = "schurhammer", repo = "lustre_virtual_list" } 10 | # links = [{ title = "Website", href = "https://gleam.run" }] 11 | 12 | [dependencies] 13 | gleam_stdlib = ">= 0.40.0 and < 1.0.0" 14 | lustre = ">= 4.5.1 and < 5.0.0" 15 | 16 | [dev-dependencies] 17 | gleeunit = ">= 1.2.0 and < 2.0.0" 18 | -------------------------------------------------------------------------------- /example/gleam.toml: -------------------------------------------------------------------------------- 1 | name = "example" 2 | version = "0.1.0" 3 | 4 | # Fill out these fields if you intend to generate HTML documentation or publish 5 | # your project to the Hex package manager. 6 | # 7 | # description = "" 8 | # licences = ["Apache-2.0"] 9 | # repository = { type = "github", user = "username", repo = "project" } 10 | # links = [{ title = "Website", href = "https://gleam.run" }] 11 | target = "javascript" 12 | 13 | [dependencies] 14 | gleam_stdlib = ">= 0.40.0 and < 1.0.0" 15 | lustre = ">= 4.5.1 and < 5.0.0" 16 | lustre_virtual_list = { path = "../" } 17 | 18 | [dev-dependencies] 19 | gleeunit = ">= 1.2.0 and < 2.0.0" 20 | lustre_dev_tools = ">= 1.6.0 and < 2.0.0" 21 | -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 🚧 example 8 | 9 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 |
53 | 54 | 55 | -------------------------------------------------------------------------------- /example/src/example.gleam: -------------------------------------------------------------------------------- 1 | import gleam/int 2 | import gleam/list 3 | import lustre 4 | import lustre/attribute.{class} 5 | import lustre/element.{type Element, text} 6 | import lustre/element/html 7 | import lustre/event.{on_click} 8 | import lustre_virtual_list.{virtual_list} 9 | 10 | pub fn main() { 11 | // 12 | // ⚠️ Important! Register the virtual list component. 13 | // 14 | lustre_virtual_list.register() 15 | 16 | let app = lustre.simple(init, update, view) 17 | let assert Ok(_) = lustre.start(app, "#app", Nil) 18 | 19 | Nil 20 | } 21 | 22 | // In this example we keep track of a list of click events. 23 | type Model { 24 | Model(events: List(String)) 25 | } 26 | 27 | fn init(_) -> Model { 28 | Model(events: []) 29 | } 30 | 31 | type Msg { 32 | ItemClick(Int) 33 | } 34 | 35 | fn update(model: Model, msg: Msg) -> Model { 36 | case msg { 37 | ItemClick(x) -> { 38 | let event = "Clicked on Item #" <> int.to_string(x) 39 | Model(events: [event, ..model.events]) 40 | } 41 | } 42 | } 43 | 44 | fn view(model: Model) -> Element(Msg) { 45 | element.fragment([ 46 | // 47 | // Create a virtual list of clickable items! 48 | // 49 | text("Clickable Items:"), 50 | virtual_list( 51 | items: list.range(1, 100_000), 52 | render: fn(item: Int) { 53 | html.div([on_click(ItemClick(item)), class("item clickable")], [ 54 | text("Item #" <> int.to_string(item)), 55 | ]) 56 | }, 57 | item_height: 24, 58 | item_count: 40, 59 | attributes: [class("list")], 60 | ), 61 | // 62 | // Create a virtual list of click events! 63 | // 64 | text("Click Events:"), 65 | virtual_list( 66 | items: model.events, 67 | render: fn(event: String) { html.div([class("item")], [text(event)]) }, 68 | item_height: 24, 69 | item_count: 20, 70 | attributes: [class("list")], 71 | ), 72 | ]) 73 | } 74 | -------------------------------------------------------------------------------- /manifest.toml: -------------------------------------------------------------------------------- 1 | # This file was generated by Gleam 2 | # You typically do not need to edit this file 3 | 4 | packages = [ 5 | { name = "gleam_erlang", version = "0.28.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "BE551521F708DCE5CB954AFBBDF08519C1C44986521FD40753608825F48FFA9E" }, 6 | { name = "gleam_json", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib", "thoas"], otp_app = "gleam_json", source = "hex", outer_checksum = "9063D14D25406326C0255BDA0021541E797D8A7A12573D849462CAFED459F6EB" }, 7 | { name = "gleam_otp", version = "0.12.1", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "BFACC1513410DF5A1617169A9CD7EA334973AC71D860A17574BA7B2EADD89A6F" }, 8 | { name = "gleam_stdlib", version = "0.40.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "86606B75A600BBD05E539EB59FABC6E307EEEA7B1E5865AFB6D980A93BCB2181" }, 9 | { name = "gleeunit", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "F7A7228925D3EE7D0813C922E062BFD6D7E9310F0BEE585D3A42F3307E3CFD13" }, 10 | { name = "lustre", version = "4.5.1", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_json", "gleam_otp", "gleam_stdlib"], otp_app = "lustre", source = "hex", outer_checksum = "B592DA442F6577143CAFA35D4506DB2018DAEED9C707A921E33559E09F001DF1" }, 11 | { name = "thoas", version = "1.2.1", build_tools = ["rebar3"], requirements = [], otp_app = "thoas", source = "hex", outer_checksum = "E38697EDFFD6E91BD12CEA41B155115282630075C2A727E7A6B2947F5408B86A" }, 12 | ] 13 | 14 | [requirements] 15 | gleam_stdlib = { version = ">= 0.40.0 and < 1.0.0" } 16 | gleeunit = { version = ">= 1.2.0 and < 2.0.0" } 17 | lustre = { version = ">= 4.5.1 and < 5.0.0" } 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Lustre Virtual List 3 | 4 | This is a basic virtual list component for rendering very large lists without performance problems. 5 | It renders only a subset of items at a time, based on which items are scrolled into the view. 6 | 7 | # How To Use 8 | 9 | Before you start your lustre app, register the virtual list component. 10 | 11 | ```gleam 12 | lustre_virtual_list.register() 13 | ``` 14 | 15 | In your view function, just call the virtual_list to render a virtual list. 16 | 17 | ```gleam 18 | virtual_list( 19 | items: list.range(1, 100_000), 20 | render: fn(item: Int) { 21 | html.div([on_click(ItemClick(item)), class("item")], [ 22 | text("Item #" <> int.to_string(item)), 23 | ]) 24 | }, 25 | item_height: 24, 26 | item_count: 40, 27 | attributes: [class("list")], 28 | ), 29 | ``` 30 | 31 | > [!NOTE] 32 | > We recommend only creating virtual lists using the `virtual_list` function, not using the element directly. 33 | 34 | 1. `items` is a list of items that will be passed to your render function. 35 | 2. `render` is a view funciton that receives one item and should return the Element to render. 36 | 3. `item_height` you must specify the height of each item so we can calculate the total size of the list. 37 | 4. `item_count` specify how many items should be rendered at a time. 38 | 5. `attributes` any additional attributes you want to add to the component, for example a class. 39 | 40 | ## Example 41 | 42 | You can run the example in the example folder with the following command. 43 | ``` 44 | cd example 45 | gleam run -m lustre/dev start 46 | ``` 47 | 48 | # lustre_virtual_list 49 | 50 | [![Package Version](https://img.shields.io/hexpm/v/lustre_virtual_list)](https://hex.pm/packages/lustre_virtual_list) 51 | [![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/lustre_virtual_list/) 52 | 53 | ## Installation 54 | 55 | If available on Hex this package can be added to your Gleam project: 56 | 57 | ```sh 58 | gleam add lustre_virtual_list 59 | ``` 60 | 61 | and its documentation can be found at . 62 | -------------------------------------------------------------------------------- /src/lustre_virtual_list.gleam: -------------------------------------------------------------------------------- 1 | import gleam/dict.{type Dict} 2 | import gleam/dynamic.{type Decoder, type Dynamic} 3 | import gleam/function 4 | import gleam/int 5 | import gleam/list 6 | import gleam/result 7 | import lustre 8 | import lustre/attribute.{type Attribute, class, style} 9 | import lustre/effect.{type Effect} 10 | import lustre/element.{type Element, element} 11 | import lustre/element/html 12 | import lustre/event 13 | 14 | /// call this at the beginning of your program to register the virtual list component 15 | pub fn register() { 16 | let app = lustre.component(init, update, view, on_attribute_change()) 17 | let _ = lustre.register(app, "lustre-virtual-list") 18 | Nil 19 | } 20 | 21 | /// render a virtual list of items 22 | /// 23 | /// items: the list of items to virtualise 24 | /// 25 | /// render: a function that renders an item 26 | /// 27 | /// item_height: you must specify the height of an item 28 | /// 29 | /// item_count: you must specify how many items to render at most 30 | /// 31 | /// attributes: optional attributes (e.g. styles) 32 | pub fn virtual_list( 33 | items items: List(a), 34 | render render_item: fn(a) -> Element(msg), 35 | item_height item_height: Int, 36 | item_count item_count: Int, 37 | attributes attributes: List(Attribute(msg)), 38 | ) -> Element(msg) { 39 | let attributes = [ 40 | style([#("display", "block"), #("height", "100%"), #("min-height", "0")]), 41 | attribute.property("items", #(items, render_item, item_height, item_count)), 42 | passthrough_item_event(), 43 | ..attributes 44 | ] 45 | element("lustre-virtual-list", attributes, []) 46 | } 47 | 48 | /// handle events on items inside the virtual list to pass them on to the parent 49 | fn passthrough_item_event() -> Attribute(msg) { 50 | use event <- event.on("item_event") 51 | event 52 | |> dynamic.field("detail", fn(x) { Ok(unsafe_coerce(x)) }) 53 | |> result.map(function.identity) 54 | } 55 | 56 | type Model(a, msg) { 57 | Model( 58 | items: List(a), 59 | render: fn(a) -> Element(msg), 60 | item_height: Int, 61 | item_count: Int, 62 | scroll_top: Int, 63 | ) 64 | } 65 | 66 | fn init(_) { 67 | let model = 68 | Model( 69 | items: [], 70 | render: fn(_) { html.div([], []) }, 71 | item_height: 0, 72 | item_count: 0, 73 | scroll_top: 0, 74 | ) 75 | #(model, effect.none()) 76 | } 77 | 78 | type Msg(a, msg) { 79 | AttrChange( 80 | items: List(a), 81 | render: fn(a) -> Element(msg), 82 | item_height: Int, 83 | item_count: Int, 84 | ) 85 | Scroll(Int) 86 | InnerMsg(msg) 87 | } 88 | 89 | fn update( 90 | model: Model(a, msg), 91 | msg: Msg(a, msg), 92 | ) -> #(Model(a, msg), Effect(Msg(a, msg))) { 93 | case msg { 94 | AttrChange(items, render, item_height, item_count) -> #( 95 | Model( 96 | ..model, 97 | items: items, 98 | render: render, 99 | item_height: item_height, 100 | item_count: item_count, 101 | ), 102 | effect.none(), 103 | ) 104 | Scroll(y) -> #(Model(..model, scroll_top: y), effect.none()) 105 | InnerMsg(msg) -> #(model, event.emit("item_event", unsafe_coerce(msg))) 106 | } 107 | } 108 | 109 | fn on_attribute_change() -> Dict(String, Decoder(Msg(a, msg))) { 110 | dict.new() 111 | |> dict.insert("items", fn(dyn) { 112 | use items <- result.try(dynamic.element(0, dynamic.dynamic)(dyn)) 113 | use render <- result.try(dynamic.element(1, dynamic.dynamic)(dyn)) 114 | use item_height <- result.try(dynamic.element(2, dynamic.int)(dyn)) 115 | use item_count <- result.try(dynamic.element(3, dynamic.int)(dyn)) 116 | let items: List(a) = unsafe_coerce(items) 117 | let render: fn(a) -> Element(msg) = unsafe_coerce(render) 118 | Ok(AttrChange(items, render, item_height, item_count)) 119 | }) 120 | } 121 | 122 | fn view(model: Model(a, msg)) -> Element(Msg(a, msg)) { 123 | case model.item_height { 124 | 0 -> html.div([attribute.class("virtual-container")], []) 125 | _ -> { 126 | let item_height = model.item_height 127 | let visible_length = model.item_count 128 | let scroll = model.scroll_top 129 | let scroll_items = scroll / item_height 130 | let items = model.items 131 | let items_length = list.length(items) 132 | let visible = 133 | items 134 | |> list.drop(scroll_items) 135 | |> list.take(visible_length) 136 | let pad_total = { items_length - visible_length } * item_height 137 | let pad_top = scroll_items * item_height 138 | let pad_bottom = pad_total - pad_top 139 | html.div( 140 | [ 141 | attribute.class("virtual-container"), 142 | style([#("height", "100%"), #("overflow-y", "auto")]), 143 | on_scroll(fn(y) { Scroll(y) }), 144 | ], 145 | [ 146 | html.div( 147 | [ 148 | attribute.class("virtual-viewport"), 149 | style([ 150 | #("padding-top", int.to_string(pad_top) <> "px"), 151 | #("padding-bottom", int.to_string(pad_bottom) <> "px"), 152 | ]), 153 | ], 154 | list.map(visible, fn(item) { 155 | html.div( 156 | [ 157 | class("virtual-item"), 158 | style([#("height", int.to_string(item_height) <> "px")]), 159 | ], 160 | [model.render(item)], 161 | ) 162 | |> element.map(InnerMsg) 163 | }), 164 | ), 165 | ], 166 | ) 167 | } 168 | } 169 | } 170 | 171 | fn on_scroll(handle: fn(Int) -> msg) { 172 | event.on("scroll", fn(dyn) { Ok(handle(get_scroll_y(dyn))) }) 173 | } 174 | 175 | @external(javascript, "./lustre_virtual_list_ffi.mjs", "getEventScrollY") 176 | fn get_scroll_y(_: Dynamic) -> Int { 177 | 0 178 | } 179 | 180 | @internal 181 | pub fn id(x) { 182 | x 183 | } 184 | 185 | @external(erlang, "lustre_virtual_list", "id") 186 | @external(javascript, "./lustre_virtual_list.mjs", "id") 187 | fn unsafe_coerce(value: a) -> b 188 | -------------------------------------------------------------------------------- /example/manifest.toml: -------------------------------------------------------------------------------- 1 | # This file was generated by Gleam 2 | # You typically do not need to edit this file 3 | 4 | packages = [ 5 | { name = "argv", version = "1.0.2", build_tools = ["gleam"], requirements = [], otp_app = "argv", source = "hex", outer_checksum = "BA1FF0929525DEBA1CE67256E5ADF77A7CDDFE729E3E3F57A5BDCAA031DED09D" }, 6 | { name = "birl", version = "1.7.1", build_tools = ["gleam"], requirements = ["gleam_stdlib", "ranger"], otp_app = "birl", source = "hex", outer_checksum = "5C66647D62BCB11FE327E7A6024907C4A17954EF22865FE0940B54A852446D01" }, 7 | { name = "directories", version = "1.1.0", build_tools = ["gleam"], requirements = ["envoy", "gleam_stdlib", "platform", "simplifile"], otp_app = "directories", source = "hex", outer_checksum = "BDA521A4EB9EE3A7894F0DC863797878E91FF5C7826F7084B2E731E208BDB076" }, 8 | { name = "envoy", version = "1.0.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "envoy", source = "hex", outer_checksum = "95FD059345AA982E89A0B6E2A3BF1CF43E17A7048DCD85B5B65D3B9E4E39D359" }, 9 | { name = "exception", version = "2.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "exception", source = "hex", outer_checksum = "F5580D584F16A20B7FCDCABF9E9BE9A2C1F6AC4F9176FA6DD0B63E3B20D450AA" }, 10 | { name = "filepath", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "EFB6FF65C98B2A16378ABC3EE2B14124168C0CE5201553DE652E2644DCFDB594" }, 11 | { name = "fs", version = "8.6.1", build_tools = ["rebar3"], requirements = [], otp_app = "fs", source = "hex", outer_checksum = "61EA2BDAEDAE4E2024D0D25C63E44DCCF65622D4402DB4A2DF12868D1546503F" }, 12 | { name = "gleam_community_ansi", version = "1.4.1", build_tools = ["gleam"], requirements = ["gleam_community_colour", "gleam_stdlib"], otp_app = "gleam_community_ansi", source = "hex", outer_checksum = "4CD513FC62523053E62ED7BAC2F36136EC17D6A8942728250A9A00A15E340E4B" }, 13 | { name = "gleam_community_colour", version = "1.4.0", build_tools = ["gleam"], requirements = ["gleam_json", "gleam_stdlib"], otp_app = "gleam_community_colour", source = "hex", outer_checksum = "795964217EBEDB3DA656F5EB8F67D7AD22872EB95182042D3E7AFEF32D3FD2FE" }, 14 | { name = "gleam_crypto", version = "1.4.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_crypto", source = "hex", outer_checksum = "8AE56026B3E05EBB1F076778478A762E9EB62B31AEEB4285755452F397029D22" }, 15 | { name = "gleam_erlang", version = "0.28.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "BE551521F708DCE5CB954AFBBDF08519C1C44986521FD40753608825F48FFA9E" }, 16 | { name = "gleam_http", version = "3.7.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_http", source = "hex", outer_checksum = "EA66440C2269F7CED0F6845E5BD0DB68095775D627FA709A841CA78A398D6D56" }, 17 | { name = "gleam_httpc", version = "3.0.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_http", "gleam_stdlib"], otp_app = "gleam_httpc", source = "hex", outer_checksum = "091CDD2BEC8092E82707BEA03FB5205A2BBBDE4A2F551E3C069E13B8BC0C428E" }, 18 | { name = "gleam_json", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib", "thoas"], otp_app = "gleam_json", source = "hex", outer_checksum = "9063D14D25406326C0255BDA0021541E797D8A7A12573D849462CAFED459F6EB" }, 19 | { name = "gleam_otp", version = "0.12.1", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "BFACC1513410DF5A1617169A9CD7EA334973AC71D860A17574BA7B2EADD89A6F" }, 20 | { name = "gleam_package_interface", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_json", "gleam_stdlib"], otp_app = "gleam_package_interface", source = "hex", outer_checksum = "CF3BFC5D0997750D9550D8D73A90F4B8D71C6C081B20ED4E70FFBE1E99AFC3C2" }, 21 | { name = "gleam_stdlib", version = "0.40.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "86606B75A600BBD05E539EB59FABC6E307EEEA7B1E5865AFB6D980A93BCB2181" }, 22 | { name = "glearray", version = "0.2.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "glearray", source = "hex", outer_checksum = "9C207E05F38D724F464FA921378DB3ABC2B0A2F5821116D8BC8B2CACC68930D5" }, 23 | { name = "gleeunit", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "F7A7228925D3EE7D0813C922E062BFD6D7E9310F0BEE585D3A42F3307E3CFD13" }, 24 | { name = "glint", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_community_ansi", "gleam_community_colour", "gleam_stdlib", "snag"], otp_app = "glint", source = "hex", outer_checksum = "A3F1B7C665FD216BE6A886D56537F0E095FB07DF62146074109270B798F8CEC4" }, 25 | { name = "glisten", version = "5.0.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_otp", "gleam_stdlib", "logging", "telemetry"], otp_app = "glisten", source = "hex", outer_checksum = "48EF7F6D1DCA877C2F49AF35CC33946C7129EEB05A114758A2CC569C708BFAF8" }, 26 | { name = "gramps", version = "2.0.3", build_tools = ["gleam"], requirements = ["gleam_crypto", "gleam_erlang", "gleam_http", "gleam_stdlib"], otp_app = "gramps", source = "hex", outer_checksum = "3CCAA6E081225180D95C79679D383BBF51C8D1FDC1B84DA1DA444F628C373793" }, 27 | { name = "hpack_erl", version = "0.3.0", build_tools = ["rebar3"], requirements = [], otp_app = "hpack", source = "hex", outer_checksum = "D6137D7079169D8C485C6962DFE261AF5B9EF60FBC557344511C1E65E3D95FB0" }, 28 | { name = "logging", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "logging", source = "hex", outer_checksum = "1098FBF10B54B44C2C7FDF0B01C1253CAFACDACABEFB4B0D027803246753E06D" }, 29 | { name = "lustre", version = "4.5.1", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_json", "gleam_otp", "gleam_stdlib"], otp_app = "lustre", source = "hex", outer_checksum = "B592DA442F6577143CAFA35D4506DB2018DAEED9C707A921E33559E09F001DF1" }, 30 | { name = "lustre_dev_tools", version = "1.6.0", build_tools = ["gleam"], requirements = ["argv", "filepath", "fs", "gleam_community_ansi", "gleam_crypto", "gleam_erlang", "gleam_http", "gleam_httpc", "gleam_json", "gleam_otp", "gleam_package_interface", "gleam_stdlib", "glint", "glisten", "mist", "simplifile", "spinner", "term_size", "tom", "wisp"], otp_app = "lustre_dev_tools", source = "hex", outer_checksum = "5A1C7D20FA2C0D77D59F259EAE0E14BB3F5359CC1DE7C5ED6922B65FFCBD4C31" }, 31 | { name = "lustre_virtual_list", version = "0.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "lustre"], source = "local", path = ".." }, 32 | { name = "marceau", version = "1.3.0", build_tools = ["gleam"], requirements = [], otp_app = "marceau", source = "hex", outer_checksum = "2D1C27504BEF45005F5DFB18591F8610FB4BFA91744878210BDC464412EC44E9" }, 33 | { name = "mist", version = "2.0.0", build_tools = ["gleam"], requirements = ["birl", "gleam_erlang", "gleam_http", "gleam_otp", "gleam_stdlib", "glisten", "gramps", "hpack_erl", "logging"], otp_app = "mist", source = "hex", outer_checksum = "981F12FC8BA0656B40099EC876D6F2BEE7B95593610F342E9AB0DC4E663A932F" }, 34 | { name = "platform", version = "1.0.0", build_tools = ["gleam"], requirements = [], otp_app = "platform", source = "hex", outer_checksum = "8339420A95AD89AAC0F82F4C3DB8DD401041742D6C3F46132A8739F6AEB75391" }, 35 | { name = "ranger", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "ranger", source = "hex", outer_checksum = "1566C272B1D141B3BBA38B25CB761EF56E312E79EC0E2DFD4D3C19FB0CC1F98C" }, 36 | { name = "repeatedly", version = "2.1.2", build_tools = ["gleam"], requirements = [], otp_app = "repeatedly", source = "hex", outer_checksum = "93AE1938DDE0DC0F7034F32C1BF0D4E89ACEBA82198A1FE21F604E849DA5F589" }, 37 | { name = "simplifile", version = "2.2.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "0DFABEF7DC7A9E2FF4BB27B108034E60C81BEBFCB7AB816B9E7E18ED4503ACD8" }, 38 | { name = "snag", version = "0.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "snag", source = "hex", outer_checksum = "54D32E16E33655346AA3E66CBA7E191DE0A8793D2C05284E3EFB90AD2CE92BCC" }, 39 | { name = "spinner", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_community_ansi", "gleam_erlang", "gleam_stdlib", "glearray", "repeatedly"], otp_app = "spinner", source = "hex", outer_checksum = "200BA3D4A04D468898E63C0D316E23F526E02514BC46454091975CB5BAE41E8F" }, 40 | { name = "telemetry", version = "1.3.0", build_tools = ["rebar3"], requirements = [], otp_app = "telemetry", source = "hex", outer_checksum = "7015FC8919DBE63764F4B4B87A95B7C0996BD539E0D499BE6EC9D7F3875B79E6" }, 41 | { name = "term_size", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "term_size", source = "hex", outer_checksum = "D00BD2BC8FB3EBB7E6AE076F3F1FF2AC9D5ED1805F004D0896C784D06C6645F1" }, 42 | { name = "thoas", version = "1.2.1", build_tools = ["rebar3"], requirements = [], otp_app = "thoas", source = "hex", outer_checksum = "E38697EDFFD6E91BD12CEA41B155115282630075C2A727E7A6B2947F5408B86A" }, 43 | { name = "tom", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "tom", source = "hex", outer_checksum = "228E667239504B57AD05EC3C332C930391592F6C974D0EFECF32FFD0F3629A27" }, 44 | { name = "wisp", version = "1.2.0", build_tools = ["gleam"], requirements = ["directories", "exception", "gleam_crypto", "gleam_erlang", "gleam_http", "gleam_json", "gleam_stdlib", "logging", "marceau", "mist", "simplifile"], otp_app = "wisp", source = "hex", outer_checksum = "F71265D2F1DE11426535A2FA1DA3B11D2FFB783B116DF9496BC8C41983EBADB4" }, 45 | ] 46 | 47 | [requirements] 48 | gleam_stdlib = { version = ">= 0.40.0 and < 1.0.0" } 49 | gleeunit = { version = ">= 1.2.0 and < 2.0.0" } 50 | lustre = { version = ">= 4.5.1 and < 5.0.0" } 51 | lustre_dev_tools = { version = ">= 1.6.0 and < 2.0.0" } 52 | lustre_virtual_list = { path = "../" } 53 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2023 - present Julian Schurhammer 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. --------------------------------------------------------------------------------