+117
-15
src/website.gleam
+117
-15
src/website.gleam
···
1
import gleam/list
2
import gleam/string
3
import gleam/uri.{type Uri}
···
9
import lustre/event
10
import lustre/ui
11
import lustre/ui/layout/cluster
12
import modem
13
import website/common
14
import website/posts
15
import website/projects
16
-
import gleam/dynamic
17
-
import lustre_http
18
19
// Main
20
···
32
current_route: Route,
33
posts: List(posts.Post(Msg)),
34
projects: List(projects.Project(Msg)),
35
-
repositories: List(Repository)
36
)
37
}
38
···
62
description: String,
63
name: String,
64
stars_count: Int,
65
)
66
}
67
68
fn get_repositories() -> Effect(Msg) {
69
let url = "https://codeberg.org/api/v1/users/naomi/repos"
70
-
let decoder = dynamic.list(of: dynamic.decode4(
71
-
Repository,
72
-
dynamic.field("avatar_url", dynamic.string),
73
-
dynamic.field("description", dynamic.string),
74
-
dynamic.field("name", dynamic.string),
75
-
dynamic.field("stars_count", dynamic.int)
76
-
))
77
lustre_http.get(url, lustre_http.expect_json(decoder, ApiGotRepositories))
78
}
79
80
fn switch_theme() -> Effect(Msg) {
81
effect.from(fn(disp) {
82
do_switch_theme()
···
101
current_route: get_route(),
102
projects: projects.all(),
103
posts: posts.all(),
104
),
105
{
106
init_theme()
107
-
modem.init(on_route_change)
108
},
109
)
110
}
···
136
)
137
ChangeDarkMode -> #(model, switch_theme())
138
DoneChangeDarkMode -> #(model, effect.none())
139
-
ApiGotRepositories -> #(model, effect.none())
140
}
141
}
142
···
219
])
220
}
221
222
-
fn view_home(_model: Model) -> Element(Msg) {
223
html.div([], [
224
html.div(
225
[
···
229
],
230
[
231
html.p([attr.class("mb-4")], [
232
element.text(
233
"Hi! I'm Naomi (or Mia), a trans girl from the UK who loves to code! I mostly make Minecraft mods, but have started
234
to begin other projects like ",
···
247
),
248
cluster.of(
249
html.div,
250
-
[attr.class("gap-4 sm:gap-20 flex flex-col sm:flex-row")],
251
[
252
ui.stack([], [
253
html.h3([attr.class("text-2xl underline")], [
···
296
"drop-shadow-md text-xl p-4 mb-4 rounded-xl bg-gray-200 dark:bg-neutral-800 dark:text-neutral-200",
297
),
298
],
299
-
[html.h1([attr.class("text-3xl m-0 text-pink-400")],[element.text("Repositories")])],
300
),
301
])
302
}
···
1
+
import birl
2
+
import gleam/dynamic
3
+
import gleam/int
4
import gleam/list
5
import gleam/string
6
import gleam/uri.{type Uri}
···
12
import lustre/event
13
import lustre/ui
14
import lustre/ui/layout/cluster
15
+
import lustre_http
16
import modem
17
import website/common
18
import website/posts
19
import website/projects
20
+
import gleam/order
21
22
// Main
23
···
35
current_route: Route,
36
posts: List(posts.Post(Msg)),
37
projects: List(projects.Project(Msg)),
38
+
repos: List(Repository),
39
)
40
}
41
···
65
description: String,
66
name: String,
67
stars_count: Int,
68
+
html_url: String,
69
+
updated_at: String,
70
+
)
71
+
}
72
+
73
+
type RepositoryBirled {
74
+
RepositoryBirled(
75
+
avatar_url: String,
76
+
description: String,
77
+
name: String,
78
+
stars_count: Int,
79
+
html_url: String,
80
+
updated_at: birl.Time,
81
)
82
}
83
84
fn get_repositories() -> Effect(Msg) {
85
let url = "https://codeberg.org/api/v1/users/naomi/repos"
86
+
let decoder =
87
+
dynamic.list(of: dynamic.decode6(
88
+
Repository,
89
+
dynamic.field("avatar_url", dynamic.string),
90
+
dynamic.field("description", dynamic.string),
91
+
dynamic.field("name", dynamic.string),
92
+
dynamic.field("stars_count", dynamic.int),
93
+
dynamic.field("html_url", dynamic.string),
94
+
dynamic.field("updated_at", dynamic.string),
95
+
))
96
lustre_http.get(url, lustre_http.expect_json(decoder, ApiGotRepositories))
97
}
98
99
+
fn sort_repos(repos: List(Repository)) -> List(RepositoryBirled) {
100
+
let repos =
101
+
repos
102
+
|> list.map(fn(repo) {
103
+
case repo {
104
+
Repository(a, b, c, d, e, time) -> {
105
+
let time = case time |> birl.parse {
106
+
Ok(time) -> time
107
+
Error(_) -> {
108
+
birl.now() |> birl.set_day(birl.Day(1, 1, 1970))
109
+
}
110
+
}
111
+
RepositoryBirled(a, b, c, d, e, time)
112
+
}
113
+
}
114
+
})
115
+
|> list.sort(fn(a, b) {
116
+
let a = a.updated_at |> birl.get_day
117
+
let b = b.updated_at |> birl.get_day
118
+
order.break_tie(order.break_tie(int.compare(a.year, b.year), int.compare(a.month, b.month)), int.compare(a.date, b.date))
119
+
})
120
+
|> list.reverse
121
+
}
122
+
123
fn switch_theme() -> Effect(Msg) {
124
effect.from(fn(disp) {
125
do_switch_theme()
···
144
current_route: get_route(),
145
projects: projects.all(),
146
posts: posts.all(),
147
+
repos: [],
148
),
149
{
150
init_theme()
151
+
effect.batch([get_repositories(), modem.init(on_route_change)])
152
},
153
)
154
}
···
180
)
181
ChangeDarkMode -> #(model, switch_theme())
182
DoneChangeDarkMode -> #(model, effect.none())
183
+
ApiGotRepositories(Ok(repos)) -> #(
184
+
Model(..model, repos: repos),
185
+
effect.none(),
186
+
)
187
+
188
+
ApiGotRepositories(Error(_)) -> #(model, effect.none())
189
}
190
}
191
···
268
])
269
}
270
271
+
fn view_home(model: Model) -> Element(Msg) {
272
html.div([], [
273
html.div(
274
[
···
278
],
279
[
280
html.p([attr.class("mb-4")], [
281
+
html.h1([attr.class("text-3xl m-0 mb-4 text-pink-400")], [
282
+
element.text("About Me"),
283
+
]),
284
element.text(
285
"Hi! I'm Naomi (or Mia), a trans girl from the UK who loves to code! I mostly make Minecraft mods, but have started
286
to begin other projects like ",
···
299
),
300
cluster.of(
301
html.div,
302
+
[attr.class("gap-4 sm:gap-20 flex flex-col md:flex-row")],
303
[
304
ui.stack([], [
305
html.h3([attr.class("text-2xl underline")], [
···
348
"drop-shadow-md text-xl p-4 mb-4 rounded-xl bg-gray-200 dark:bg-neutral-800 dark:text-neutral-200",
349
),
350
],
351
+
[
352
+
html.h1([attr.class("text-3xl m-0 mb-4 text-pink-400")], [
353
+
element.text("Repositories"),
354
+
]),
355
+
html.div(
356
+
[attr.class("grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4")],
357
+
model.repos
358
+
|> sort_repos
359
+
|> list.map(fn(repo) {
360
+
let url = case repo.avatar_url {
361
+
"" -> "https://avatars.githubusercontent.com/u/95784613?v=4"
362
+
a -> a
363
+
}
364
+
html.a(
365
+
[
366
+
attr.class(
367
+
"
368
+
drop-shadow-lg rounded-xl p-4
369
+
dark:text-neutral-200
370
+
bg-gray-300 dark:bg-neutral-700
371
+
hover:bg-slate-300 dark:hover:bg-slate-700
372
+
",
373
+
),
374
+
attr.href(repo.html_url),
375
+
],
376
+
[
377
+
html.div([attr.class("flex items-center")], [
378
+
html.img([
379
+
attr.src(url),
380
+
attr.class("drop-shadow-md rounded-xl max-w-16 max-h-16"),
381
+
]),
382
+
html.div([attr.class("m-2")], [
383
+
html.h2([], [element.text(repo.name)]),
384
+
html.h2([], [
385
+
element.text(
386
+
"⭐ " <> repo.stars_count |> int.to_string,
387
+
),
388
+
]),
389
+
]),
390
+
]),
391
+
html.div([], [
392
+
case repo.description {
393
+
"" -> html.p([attr.class("text-neutral-500 dark:text-neutral-400")], [element.text("No description")])
394
+
a -> element.text(a)
395
+
}
396
+
]),
397
+
],
398
+
)
399
+
}),
400
+
),
401
+
],
402
),
403
])
404
}