silly little doodles
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

MVP

nnuuvv d29ef651

+381
+23
.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@v4 15 + - uses: erlef/setup-beam@v1 16 + with: 17 + otp-version: "27.1.2" 18 + gleam-version: "1.12.0" 19 + rebar3-version: "3" 20 + # elixir-version: "1" 21 + - run: gleam deps download 22 + - run: gleam test 23 + - run: gleam format --check src test
+8
.gitignore
··· 1 + *.beam 2 + *.ez 3 + /build 4 + erl_crash.dump 5 + 6 + #Added automatically by Lustre Dev Tools 7 + /.lustre 8 + /dist
+7
README.md
··· 1 + # doodler 2 + 3 + ## Development 4 + 5 + ```sh 6 + gleam run -m lustre/dev start 7 + ```
+25
gleam.toml
··· 1 + name = "doodler" 2 + version = "1.0.0" 3 + target = "javascript" 4 + 5 + # Fill out these fields if you intend to generate HTML documentation or publish 6 + # your project to the Hex package manager. 7 + # 8 + # description = "" 9 + # licences = ["Apache-2.0"] 10 + # repository = { type = "github", user = "", repo = "" } 11 + # links = [{ title = "Website", href = "" }] 12 + # 13 + # For a full reference of all the available options, you can have a look at 14 + # https://gleam.run/writing-gleam/gleam-toml/. 15 + 16 + [javascript] 17 + runtime = "bun" 18 + 19 + [dependencies] 20 + gleam_stdlib = ">= 0.44.0 and < 2.0.0" 21 + lustre = ">= 5.3.5 and < 6.0.0" 22 + 23 + [dev-dependencies] 24 + gleeunit = ">= 1.0.0 and < 2.0.0" 25 + lustre_dev_tools = ">= 2.1.3 and < 3.0.0"
+49
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 = "booklet", version = "1.0.2", build_tools = ["gleam"], requirements = [], otp_app = "booklet", source = "hex", outer_checksum = "279247A5FD6388B34058A6109E99D7E7C7A4CA3EC8A13912536A05E98BC2D275" }, 7 + { name = "directories", version = "1.2.0", build_tools = ["gleam"], requirements = ["envoy", "gleam_stdlib", "platform", "simplifile"], otp_app = "directories", source = "hex", outer_checksum = "D13090CFCDF6759B87217E8DDD73A75903A700148A82C1D33799F333E249BF9E" }, 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.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "exception", source = "hex", outer_checksum = "329D269D5C2A314F7364BD2711372B6F2C58FA6F39981572E5CA68624D291F8C" }, 10 + { name = "filepath", version = "1.1.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "B06A9AF0BF10E51401D64B98E4B627F1D2E48C154967DA7AF4D0914780A6D40A" }, 11 + { name = "gleam_community_ansi", version = "1.4.3", build_tools = ["gleam"], requirements = ["gleam_community_colour", "gleam_regexp", "gleam_stdlib"], otp_app = "gleam_community_ansi", source = "hex", outer_checksum = "8A62AE9CC6EA65BEA630D95016D6C07E4F9973565FA3D0DE68DC4200D8E0DD27" }, 12 + { name = "gleam_community_colour", version = "2.0.2", build_tools = ["gleam"], requirements = ["gleam_json", "gleam_stdlib"], otp_app = "gleam_community_colour", source = "hex", outer_checksum = "E34DD2C896AC3792151EDA939DA435FF3B69922F33415ED3C4406C932FBE9634" }, 13 + { name = "gleam_crypto", version = "1.5.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_crypto", source = "hex", outer_checksum = "50774BAFFF1144E7872814C566C5D653D83A3EBF23ACC3156B757A1B6819086E" }, 14 + { name = "gleam_erlang", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "1124AD3AA21143E5AF0FC5CF3D9529F6DB8CA03E43A55711B60B6B7B3874375C" }, 15 + { name = "gleam_http", version = "4.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_http", source = "hex", outer_checksum = "82EA6A717C842456188C190AFB372665EA56CE13D8559BF3B1DD9E40F619EE0C" }, 16 + { name = "gleam_httpc", version = "5.0.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_http", "gleam_stdlib"], otp_app = "gleam_httpc", source = "hex", outer_checksum = "C545172618D07811494E97AAA4A0FB34DA6F6D0061FDC8041C2F8E3BE2B2E48F" }, 17 + { name = "gleam_json", version = "3.0.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_json", source = "hex", outer_checksum = "874FA3C3BB6E22DD2BB111966BD40B3759E9094E05257899A7C08F5DE77EC049" }, 18 + { name = "gleam_otp", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "BA6A294E295E428EC1562DC1C11EA7530DCB981E8359134BEABC8493B7B2258E" }, 19 + { name = "gleam_regexp", version = "1.1.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_regexp", source = "hex", outer_checksum = "9C215C6CA84A5B35BB934A9B61A9A306EC743153BE2B0425A0D032E477B062A9" }, 20 + { name = "gleam_stdlib", version = "0.65.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "7C69C71D8C493AE11A5184828A77110EB05A7786EBF8B25B36A72F879C3EE107" }, 21 + { name = "gleam_time", version = "1.4.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_time", source = "hex", outer_checksum = "DCDDC040CE97DA3D2A925CDBBA08D8A78681139745754A83998641C8A3F6587E" }, 22 + { name = "gleam_yielder", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_yielder", source = "hex", outer_checksum = "8E4E4ECFA7982859F430C57F549200C7749823C106759F4A19A78AEA6687717A" }, 23 + { name = "gleeunit", version = "1.7.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "CD701726CBCE5588B375D157B4391CFD0F2F134CD12D9B6998A395484DE05C58" }, 24 + { name = "glint", version = "1.2.1", build_tools = ["gleam"], requirements = ["gleam_community_ansi", "gleam_community_colour", "gleam_stdlib", "snag"], otp_app = "glint", source = "hex", outer_checksum = "2214C7CEFDE457CEE62140C3D4899B964E05236DA74E4243DFADF4AF29C382BB" }, 25 + { name = "glisten", version = "8.0.1", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_otp", "gleam_stdlib", "logging", "telemetry"], otp_app = "glisten", source = "hex", outer_checksum = "534BB27C71FB9E506345A767C0D76B17A9E9199934340C975DC003C710E3692D" }, 26 + { name = "gramps", version = "6.0.0", build_tools = ["gleam"], requirements = ["gleam_crypto", "gleam_erlang", "gleam_http", "gleam_stdlib"], otp_app = "gramps", source = "hex", outer_checksum = "8B7195978FBFD30B43DF791A8A272041B81E45D245314D7A41FC57237AA882A0" }, 27 + { name = "group_registry", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_otp", "gleam_stdlib"], otp_app = "group_registry", source = "hex", outer_checksum = "BC798A53D6F2406DB94E27CB45C57052CB56B32ACF7CC16EA20F6BAEC7E36B90" }, 28 + { name = "houdini", version = "1.2.0", build_tools = ["gleam"], requirements = [], otp_app = "houdini", source = "hex", outer_checksum = "5DB1053F1AF828049C2B206D4403C18970ABEF5C18671CA3C2D2ED0DD64F6385" }, 29 + { name = "hpack_erl", version = "0.3.0", build_tools = ["rebar3"], requirements = [], otp_app = "hpack", source = "hex", outer_checksum = "D6137D7079169D8C485C6962DFE261AF5B9EF60FBC557344511C1E65E3D95FB0" }, 30 + { name = "justin", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "justin", source = "hex", outer_checksum = "7FA0C6DB78640C6DC5FBFD59BF3456009F3F8B485BF6825E97E1EB44E9A1E2CD" }, 31 + { name = "logging", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "logging", source = "hex", outer_checksum = "1098FBF10B54B44C2C7FDF0B01C1253CAFACDACABEFB4B0D027803246753E06D" }, 32 + { name = "lustre", version = "5.3.5", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_json", "gleam_otp", "gleam_stdlib", "houdini"], otp_app = "lustre", source = "hex", outer_checksum = "5CBB5DD2849D8316A2101792FC35AEB58CE4B151451044A9C2A2A70A2F7FCEB8" }, 33 + { name = "lustre_dev_tools", version = "2.1.3", build_tools = ["gleam"], requirements = ["argv", "booklet", "filepath", "gleam_community_ansi", "gleam_crypto", "gleam_erlang", "gleam_http", "gleam_httpc", "gleam_json", "gleam_otp", "gleam_regexp", "gleam_stdlib", "glint", "group_registry", "justin", "lustre", "mist", "polly", "simplifile", "tom", "wisp"], otp_app = "lustre_dev_tools", source = "hex", outer_checksum = "0C5E363B2BDFF8A87586BA251EF8DEA4DB8B35C4CC931476556BCC63D0355046" }, 34 + { name = "marceau", version = "1.3.0", build_tools = ["gleam"], requirements = [], otp_app = "marceau", source = "hex", outer_checksum = "2D1C27504BEF45005F5DFB18591F8610FB4BFA91744878210BDC464412EC44E9" }, 35 + { name = "mist", version = "5.0.3", build_tools = ["gleam"], requirements = ["exception", "gleam_erlang", "gleam_http", "gleam_otp", "gleam_stdlib", "gleam_yielder", "glisten", "gramps", "hpack_erl", "logging"], otp_app = "mist", source = "hex", outer_checksum = "7C4BE717A81305323C47C8A591E6B9BA4AC7F56354BF70B4D3DF08CC01192668" }, 36 + { name = "platform", version = "1.0.0", build_tools = ["gleam"], requirements = [], otp_app = "platform", source = "hex", outer_checksum = "8339420A95AD89AAC0F82F4C3DB8DD401041742D6C3F46132A8739F6AEB75391" }, 37 + { name = "polly", version = "2.1.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib", "simplifile"], otp_app = "polly", source = "hex", outer_checksum = "1BA4D0ACE9BCF52AEA6AD9DE020FD8220CCA399A379E50A1775FC5C1204FCF56" }, 38 + { name = "simplifile", version = "2.3.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "0A868DAC6063D9E983477981839810DC2E553285AB4588B87E3E9C96A7FB4CB4" }, 39 + { name = "snag", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "snag", source = "hex", outer_checksum = "7E9F06390040EB5FAB392CE642771484136F2EC103A92AE11BA898C8167E6E17" }, 40 + { name = "telemetry", version = "1.3.0", build_tools = ["rebar3"], requirements = [], otp_app = "telemetry", source = "hex", outer_checksum = "7015FC8919DBE63764F4B4B87A95B7C0996BD539E0D499BE6EC9D7F3875B79E6" }, 41 + { name = "tom", version = "2.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "gleam_time"], otp_app = "tom", source = "hex", outer_checksum = "74D0C5A3761F7A7D06994755D4D5AD854122EF8E9F9F76A3E7547606D8C77091" }, 42 + { name = "wisp", version = "2.1.0", build_tools = ["gleam"], requirements = ["directories", "exception", "filepath", "gleam_crypto", "gleam_erlang", "gleam_http", "gleam_json", "gleam_stdlib", "houdini", "logging", "marceau", "mist", "simplifile"], otp_app = "wisp", source = "hex", outer_checksum = "362BDDD11BF48EB38CDE51A73BC7D1B89581B395CA998E3F23F11EC026151C54" }, 43 + ] 44 + 45 + [requirements] 46 + gleam_stdlib = { version = ">= 0.44.0 and < 2.0.0" } 47 + gleeunit = { version = ">= 1.0.0 and < 2.0.0" } 48 + lustre = { version = ">= 5.3.5 and < 6.0.0" } 49 + lustre_dev_tools = { version = ">= 2.1.3 and < 3.0.0" }
+249
src/doodler.gleam
··· 1 + import gleam/int 2 + import gleam/io 3 + import gleam/list 4 + import gleam/option.{type Option, None, Some} 5 + import lustre 6 + import lustre/attribute 7 + import lustre/element.{text} 8 + import lustre/element/html.{button, div, p} 9 + import lustre/element/svg 10 + import lustre/event.{on_click} 11 + 12 + const size = 500 13 + 14 + pub fn main() { 15 + let app = lustre.simple(init, update, view) 16 + let assert Ok(_) = lustre.start(app, "#app", Nil) 17 + 18 + Nil 19 + } 20 + 21 + fn init(_flags) { 22 + Model( 23 + [ 24 + Point(0, -1), 25 + Point(-1, 0), 26 + Point(0, 0), 27 + Point(0, 1), 28 + Point(1, 0), 29 + Point(2, 0), 30 + ], 31 + [], 32 + None, 33 + ) 34 + } 35 + 36 + type Model { 37 + Model(points: List(Point), edges: List(Edge), selected: Option(Point)) 38 + } 39 + 40 + type Point { 41 + Point(x: Int, y: Int) 42 + } 43 + 44 + type Edge { 45 + Edge(a: Point, b: Point) 46 + } 47 + 48 + type Msg { 49 + UserClickedPoint(Point) 50 + } 51 + 52 + fn update(model: Model, msg: Msg) { 53 + case msg { 54 + // no point selected yet 55 + UserClickedPoint(second_point) -> { 56 + case model { 57 + // no point selected, select the clicked one 58 + Model(_, _, None) -> Model(..model, selected: Some(second_point)) 59 + // same point clicked again, deselect it 60 + Model(_, _, Some(first_point)) if second_point == first_point -> 61 + Model(..model, selected: None) 62 + 63 + // different point selected, add / remove that edge 64 + Model(_, _, Some(first_point)) -> { 65 + // doesnt work 66 + let thing = 67 + model.edges 68 + |> list.partition(fn(edge) { 69 + { 70 + points_equal(edge.a, first_point) 71 + && points_equal(edge.b, second_point) 72 + } 73 + || { 74 + points_equal(edge.a, second_point) 75 + && points_equal(edge.b, first_point) 76 + } 77 + }) 78 + |> echo 79 + 80 + let edges = case thing { 81 + // the new edge wasnt found in the existing ones, add it 82 + #([], edges) -> [Edge(first_point, second_point), ..edges] 83 + // the new edge was found in the existing ones, remove it 84 + #([_, ..], edges) -> edges 85 + } 86 + 87 + Model(..model, edges:, selected: None) 88 + |> echo 89 + } 90 + } 91 + } 92 + } 93 + } 94 + 95 + fn points_equal(first_point: Point, second_point: Point) -> Bool { 96 + first_point.x == second_point.x && first_point.y == second_point.y 97 + } 98 + 99 + @external(javascript, "./doodler.js", "viewport_height") 100 + pub fn viewport_height() -> Int 101 + 102 + @external(javascript, "./doodler.js", "viewport_width") 103 + pub fn viewport_width() -> Int 104 + 105 + fn count_x_y(points: List(Point)) -> #(Int, Int) { 106 + let #(x, y) = 107 + points 108 + |> list.fold(#(0, 0), fn(counts, point) { 109 + let #(max_x, max_y) = counts 110 + 111 + let x = int.absolute_value(point.x) 112 + let y = int.absolute_value(point.y) 113 + 114 + case x, y { 115 + x, y if x > max_x && y > max_y -> #(x, y) 116 + x, _y if x > max_x -> #(x, max_y) 117 + _x, y if y > max_y -> #(max_x, y) 118 + _, _ -> counts 119 + } 120 + }) 121 + 122 + #(x * 2 + 1, y * 2 + 1) 123 + } 124 + 125 + fn view(model: Model) { 126 + let #(x, y) = 127 + model.points 128 + |> count_x_y 129 + 130 + let vp_width = viewport_width() 131 + let vp_height = viewport_width() 132 + 133 + let width = vp_width / x 134 + let height = vp_height / y 135 + 136 + let size = int.min(width, height) 137 + 138 + div( 139 + [], 140 + list.append( 141 + [ 142 + svg.svg( 143 + [ 144 + attribute.style("position", "fixed"), 145 + attribute.width(vp_width), 146 + attribute.height(vp_height), 147 + ], 148 + list.map(model.edges, view_edge(_, size, vp_width / 2, vp_height / 2)), 149 + ), 150 + ], 151 + list.map(model.points, view_point( 152 + _, 153 + size, 154 + vp_width / 2, 155 + vp_height / 2, 156 + False, 157 + )), 158 + ) 159 + |> list.append([ 160 + view_selected(model.selected, size, vp_width / 2, vp_height / 2), 161 + ]), 162 + ) 163 + } 164 + 165 + fn view_selected( 166 + selected: Option(Point), 167 + size: Int, 168 + x_offset: Int, 169 + y_offset: Int, 170 + ) { 171 + case selected { 172 + None -> div([], []) 173 + Some(point) -> view_point(point, size, x_offset, y_offset, True) 174 + } 175 + } 176 + 177 + fn view_edge(edge: Edge, size: Int, x_offset: Int, y_offset: Int) { 178 + let Edge(a, b) = edge 179 + svg.line([ 180 + attribute.attribute( 181 + "x1", 182 + int.to_string(calculate_offset(size, a.x, x_offset)), 183 + ), 184 + attribute.attribute( 185 + "y1", 186 + int.to_string(calculate_offset(size, a.y, y_offset)), 187 + ), 188 + attribute.attribute( 189 + "x2", 190 + int.to_string(calculate_offset(size, b.x, x_offset)), 191 + ), 192 + attribute.attribute( 193 + "y2", 194 + int.to_string(calculate_offset(size, b.y, y_offset)), 195 + ), 196 + attribute.styles([ 197 + #("stroke", "black"), 198 + #("stroke-width", "5"), 199 + ]), 200 + ]) 201 + } 202 + 203 + fn view_point( 204 + point: Point, 205 + size: Int, 206 + x_offset: Int, 207 + y_offset: Int, 208 + selected: Bool, 209 + ) { 210 + let selected_attrs = case selected { 211 + False -> [ 212 + attribute.style("background-color", "#000000"), 213 + ] 214 + True -> [ 215 + attribute.style("background-color", "green"), 216 + ] 217 + } 218 + 219 + div( 220 + [ 221 + on_click(UserClickedPoint(point)), 222 + attribute.style("width", int.to_string(size / 5) <> "px"), 223 + attribute.style("height", int.to_string(size / 5) <> "px"), 224 + 225 + attribute.style("color", "#FFFFFF"), 226 + 227 + attribute.style("border-radius", "50%"), 228 + attribute.style("border", "none"), 229 + attribute.style("text-align", "center"), 230 + attribute.style("text-decoration", "none"), 231 + 232 + attribute.style("position", "fixed"), 233 + attribute.style( 234 + "top", 235 + int.to_string(calculate_offset(size, point.y, y_offset)) <> "px", 236 + ), 237 + attribute.style( 238 + "left", 239 + int.to_string(calculate_offset(size, point.x, x_offset)) <> "px", 240 + ), 241 + ..selected_attrs 242 + ], 243 + [], 244 + ) 245 + } 246 + 247 + fn calculate_offset(size, coord, offset) { 248 + { size * coord } + offset 249 + }
+7
src/doodler.js
··· 1 + export function viewport_width() { 2 + return document.documentElement.clientWidth 3 + } 4 + 5 + export function viewport_height() { 6 + return document.documentElement.clientHeight 7 + }
+13
test/doodler_test.gleam
··· 1 + import gleeunit 2 + 3 + pub fn main() -> Nil { 4 + gleeunit.main() 5 + } 6 + 7 + // gleeunit test functions end in `_test` 8 + pub fn hello_world_test() { 9 + let name = "Joe" 10 + let greeting = "Hello, " <> name <> "!" 11 + 12 + assert greeting == "Hello, Joe!" 13 + }