-module(bright). -compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch]). -export([init/2, update/2, compute/2, schedule/2, unwrap/1, data/1, computed/1, step/2, start/2, lazy_compute/3, lazy_schedule/3]). -export_type([bright/2]). -if(?OTP_RELEASE >= 27). -define(MODULEDOC(Str), -moduledoc(Str)). -define(DOC(Str), -doc(Str)). -else. -define(MODULEDOC(Str), -compile([])). -define(DOC(Str), -compile([])). -endif. -opaque bright(MWK, MWL) :: {bright, MWK, MWL, list(gleam@dynamic:dynamic_()), list(gleam@dynamic:dynamic_())}. -file("src/bright.gleam", 21). ?DOC( " Creates the initial `Bright`. `data` & `computed` should be initialised with\n" " their correct empty initial state.\n" ). -spec init(MWM, MWN) -> bright(MWM, MWN). init(Data, Computed) -> {bright, Data, Computed, [], []}. -file("src/bright.gleam", 60). ?DOC( " Update data & effects during update cycle. Use it a way to update your data\n" " stored in `Bright`, and chain them with other `bright` calls.\n" "\n" " ```gleam\n" " pub fn update(model: Bright(data, computed), msg: Msg) {\n" " use model <- bright.start(model)\n" " // Run an update, and returns #(Bright(data, computed), Effect(msg)).\n" " bright.update(model, update_data(_, msg))\n" " }\n" " ```\n" ). -spec update( {bright(MXD, MXE), lustre@effect:effect(MXH)}, fun((MXD) -> {MXD, lustre@effect:effect(MXH)}) ) -> {bright(MXD, MXE), lustre@effect:effect(MXH)}. update(Bright, Update_) -> {Bright@1, Effects} = Bright, {Data, Effect} = Update_(erlang:element(2, Bright@1)), {begin _record = Bright@1, {bright, Data, erlang:element(3, _record), erlang:element(4, _record), erlang:element(5, _record)} end, lustre@effect:batch([Effects, Effect])}. -file("src/bright.gleam", 83). ?DOC( " Derives data from the `data` state, and potentially the current `computed`\n" " state. `compute` will run **at every render**, so be careful with computations\n" " as they can block paint or actors.\n" "\n" " ```gleam\n" " pub fn update(model: Bright(data, computed), msg: Msg) {\n" " use model <- bright.start(model)\n" " model\n" " |> bright.update(update_data(_, msg))\n" " |> bright.compute(fn (d, c) { Computed(..c, field1: computation1(d)) })\n" " |> bright.compute(fn (d, c) { Computed(..c, field2: computation2(d)) })\n" " |> bright.compute(fn (d, c) { Computed(..c, field3: computation3(d)) })\n" " }\n" " ```\n" ). -spec compute( {bright(MXN, MXO), lustre@effect:effect(MXR)}, fun((MXN, MXO) -> MXO) ) -> {bright(MXN, MXO), lustre@effect:effect(MXR)}. compute(Bright, Compute_) -> gleam@pair:map_first( Bright, fun(Bright@1) -> _pipe = Compute_( erlang:element(2, Bright@1), erlang:element(3, Bright@1) ), (fun(Computed) -> _record = Bright@1, {bright, erlang:element(2, _record), Computed, erlang:element(4, _record), erlang:element(5, _record)} end)(_pipe) end ). -file("src/bright.gleam", 109). ?DOC( " Plugs in existing `data` and `computed` state, to issue some side-effects,\n" " when your application needs to run side-effects depending on the current state.\n" "\n" " ```gleam\n" " pub fn update(model: Bright(data, computed), msg: Msg) {\n" " use model <- bright.start(model)\n" " model\n" " |> bright.update(update_data(_, msg))\n" " |> bright.schedule(model, fn (data, computed) {\n" " use dispatch <- effect.from\n" " case data.field == 10 {\n" " True -> dispatch(my_msg)\n" " False -> Nil\n" " }\n" " })\n" " }\n" " ```\n" ). -spec schedule( {bright(MXW, MXX), lustre@effect:effect(MYA)}, fun((MXW, MXX) -> lustre@effect:effect(MYA)) ) -> {bright(MXW, MXX), lustre@effect:effect(MYA)}. schedule(Bright, Schedule_) -> {Bright@1, Effects} = Bright, Effect = Schedule_(erlang:element(2, Bright@1), erlang:element(3, Bright@1)), {Bright@1, lustre@effect:batch([Effects, Effect])}. -file("src/bright.gleam", 196). ?DOC( " Extracts `data` & `computed` states from `Bright`.\n" "\n" " ```gleam\n" " pub fn view(model: Bright(data, computed)) {\n" " let #(data, computed) = bright.unwrap(model)\n" " html.div([], [\n" " // Use data or computed here.\n" " ])\n" " }\n" " ```\n" ). -spec unwrap(bright(MZB, MZC)) -> {MZB, MZC}. unwrap(Bright) -> {erlang:element(2, Bright), erlang:element(3, Bright)}. -file("src/bright.gleam", 210). ?DOC( " Extracts `data` state from `Bright`.\n" "\n" " ```gleam\n" " pub fn view(model: Bright(data, computed)) {\n" " let data = bright.data(model)\n" " html.div([], [\n" " // Use data here.\n" " ])\n" " }\n" " ```\n" ). -spec data(bright(MZF, any())) -> MZF. data(Bright) -> erlang:element(2, Bright). -file("src/bright.gleam", 224). ?DOC( " Extracts `computed` state from `Bright`.\n" "\n" " ```gleam\n" " pub fn view(model: Bright(data, computed)) {\n" " let computed = bright.computed(model)\n" " html.div([], [\n" " // Use computed here.\n" " ])\n" " }\n" " ```\n" ). -spec computed(bright(any(), MZK)) -> MZK. computed(Bright) -> erlang:element(3, Bright). -file("src/bright.gleam", 246). ?DOC( " Allows to run multiple `update` on multiple `Bright` in the same update cycle.\n" " Every call to step with compute a new `Bright`, and will let you chain the\n" " steps.\n" "\n" " ```gleam\n" " pub type Model {\n" " Model(\n" " fst_bright: Bright(data, computed),\n" " snd_bright: Bright(data, computed),\n" " )\n" " }\n" "\n" " fn update(model: Model, msg: Msg) {\n" " use fst_bright <- bright.step(update_fst(model.fst_bright, msg))\n" " use snd_bright <- bright.step(update_snd(model.snd_bright, msg))\n" " #(Model(fst_bright:, snd_bright:), effect.none())\n" " }\n" " ```\n" ). -spec step( {bright(MZN, MZO), lustre@effect:effect(MZR)}, fun((bright(MZN, MZO)) -> {MZV, lustre@effect:effect(MZR)}) ) -> {MZV, lustre@effect:effect(MZR)}. step(Bright, Next) -> {Bright@1, Effs} = Bright, {Model, Effs_} = Next(Bright@1), {Model, lustre@effect:batch([Effs, Effs_])}. -file("src/bright.gleam", 281). -spec panic_if_different_computations_count(list(any()), list(any())) -> nil. panic_if_different_computations_count(Old_computations, Computations) -> Count = erlang:length(Old_computations), gleam@bool:guard( Count =:= 0, nil, fun() -> Is_same_count = Count =:= erlang:length(Computations), gleam@bool:guard( Is_same_count, nil, fun() -> erlang:error(#{gleam_error => panic, message => <<"Memoized computed should be consistent over time, otherwise memo can not work."/utf8>>, module => <<"bright"/utf8>>, function => <<"panic_if_different_computations_count"/utf8>>, line => 289}) end ) end ). -file("src/bright.gleam", 38). ?DOC( " Start the Bright update cycle. Use it as a way to trigger the start of `Bright`\n" " computations, and chain them with other `bright` calls. `start` handles all\n" " of the hard work, of turning a `Bright(data, computed)` into a\n" " `#(Bright(data, computed), Effect(msg))`, and will take care that your\n" " `Bright(data, computed)` is always consistent over multiple update cycles.\n" "\n" " ```gleam\n" " pub fn update(model: Bright(data, computed), msg: Msg) {\n" " // Starts the update cycle, and returns #(Bright(data, computed), Effect(msg)).\n" " use model <- bright.start(model)\n" " bright.update(model, update_data(_, msg))\n" " }\n" " ```\n" ). -spec start( bright(MWP, MWQ), fun(({bright(MWP, MWQ), lustre@effect:effect(MWV)}) -> {bright(MWP, MWQ), lustre@effect:effect(MWV)}) ) -> {bright(MWP, MWQ), lustre@effect:effect(MWV)}. start(Bright, Next) -> Old_computations = erlang:element(5, Bright), gleam@pair:map_first( Next({Bright, lustre@effect:none()}), fun(New_data) -> panic_if_different_computations_count( Old_computations, erlang:element(4, New_data) ), Past_selections = lists:reverse(erlang:element(4, New_data)), _record = New_data, {bright, erlang:element(2, _record), erlang:element(3, _record), [], Past_selections} end ). -file("src/bright.gleam", 296). ?DOC( " Optimization on JS, to ensure that two data sharing the referential equality\n" " will shortcut the comparison. Useful when performance are a thing in client\n" " browser. Otherwise, rely on Erlang equality.\n" ). -spec are_dependencies_equal(any(), any()) -> boolean(). are_dependencies_equal(A, B) -> gleam_stdlib:identity(A) =:= gleam_stdlib:identity(B). -file("src/bright.gleam", 255). -spec lazy_wrap( {bright(MZY, MZZ), lustre@effect:effect(NAC)}, fun((MZY) -> NAE), fun(({bright(MZY, MZZ), lustre@effect:effect(NAC)}, fun((MZY, MZZ) -> NAI)) -> {bright(MZY, MZZ), lustre@effect:effect(NAC)}), fun((MZY, MZZ, NAE) -> NAI) ) -> {bright(MZY, MZZ), lustre@effect:effect(NAC)}. lazy_wrap(Bright, Selector, Setter, Compute_) -> Selected_data = Selector(erlang:element(2, (erlang:element(1, Bright)))), Selections = [gleam_stdlib:identity(Selected_data) | erlang:element(4, (erlang:element(1, Bright)))], Compute_@1 = fun(Data, Computed) -> Compute_(Data, Computed, Selected_data) end, Bright@1 = {begin _record = erlang:element(1, Bright), {bright, erlang:element(2, _record), erlang:element(3, _record), Selections, erlang:element(5, _record)} end, erlang:element(2, Bright)}, case erlang:element(5, (erlang:element(1, Bright@1))) of [] -> Setter(Bright@1, Compute_@1); [Value | Past_selections] -> _pipe = {begin _record@1 = erlang:element(1, Bright@1), {bright, erlang:element(2, _record@1), erlang:element(3, _record@1), erlang:element(4, _record@1), Past_selections} end, erlang:element(2, Bright@1)}, case are_dependencies_equal(Value, Selected_data) of true -> fun gleam@function:identity/1; false -> fun(_capture) -> Setter(_capture, Compute_@1) end end(_pipe) end. -file("src/bright.gleam", 142). ?DOC( " Derives data like [`compute`](#compute) lazily. `lazy_compute` accepts a\n" " selector as second argument. Each time the selector returns a different data\n" " than previous run, the computation will run. Otherwise, nothing happens.\n" " The computation function will receive `data`, `computed` and the selected\n" " data (i.e. the result from your selector function), in case accessing the\n" " selected data is needed.\n" "\n" " ```gleam\n" " pub fn update(model: Bright(data, computed), msg: Msg) {\n" " use model <- bright.start(model)\n" " model\n" " |> bright.update(update_data(_, msg))\n" " // Here, e is always the result data.field / 10 (the result from selector).\n" " |> bright.lazy_compute(selector, fn (d, c, e) { Computed(..c, field1: computation1(d, e)) })\n" " |> bright.lazy_compute(selector, fn (d, c, e) { Computed(..c, field2: computation2(d, e)) })\n" " |> bright.lazy_compute(selector, fn (d, c, e) { Computed(..c, field3: computation3(d, e)) })\n" " }\n" "\n" " /// Use it with lazy_compute to recompute only when the field when\n" " /// { old_data.field / 10 } != { data.field / 10 }\n" " fn selector(d, _) {\n" " d.field / 10\n" " }\n" " ```\n" ). -spec lazy_compute( {bright(MYG, MYH), lustre@effect:effect(MYK)}, fun((MYG) -> MYM), fun((MYG, MYH, MYM) -> MYH) ) -> {bright(MYG, MYH), lustre@effect:effect(MYK)}. lazy_compute(Bright, Selector, Compute_) -> lazy_wrap(Bright, Selector, fun compute/2, Compute_). -file("src/bright.gleam", 178). ?DOC( " Plugs in existing `data` like [`schedule`](#schedule) lazily. `lazy_schedule` accepts\n" " a selector as second argument. Each time the selector returns a different data\n" " than previous run, the computation will run. Otherwise, nothing happens.\n" " The scheduling function will receive `data`, `computed` and the selected\n" " data (i.e. the result from your selector function), in case accessing the\n" " selected data is needed.\n" "\n" " ```gleam\n" " pub fn update(model: Bright(data, computed), msg: Msg) {\n" " use model <- bright.start(model)\n" " model\n" " |> bright.update(update_data(_, msg))\n" " // e is equal to d.field / 10 (the result from selector).\n" " |> bright.lazy_schedule(selector, fn (data, computed, selected) {\n" " use dispatch <- effect.from\n" " case d.field == 10 {\n" " True -> dispatch(my_msg)\n" " False -> Nil\n" " }\n" " })\n" " }\n" "\n" " /// Use it with lazy_schedule to recompute only when the field when\n" " /// { old_data.field / 10 } != { data.field / 10 }\n" " fn selector(d, _) {\n" " d.field / 10\n" " }\n" " ```\n" ). -spec lazy_schedule( {bright(MYQ, MYR), lustre@effect:effect(MYU)}, fun((MYQ) -> MYW), fun((MYQ, MYR, MYW) -> lustre@effect:effect(MYU)) ) -> {bright(MYQ, MYR), lustre@effect:effect(MYU)}. lazy_schedule(Bright, Selector, Schedule_) -> lazy_wrap(Bright, Selector, fun schedule/2, Schedule_).