+117
-15
src/website.gleam
+117
-15
src/website.gleam
···
1
+
import birl
2
+
import gleam/dynamic
3
+
import gleam/int
1
4
import gleam/list
2
5
import gleam/string
3
6
import gleam/uri.{type Uri}
···
9
12
import lustre/event
10
13
import lustre/ui
11
14
import lustre/ui/layout/cluster
15
+
import lustre_http
12
16
import modem
13
17
import website/common
14
18
import website/posts
15
19
import website/projects
16
-
import gleam/dynamic
17
-
import lustre_http
20
+
import gleam/order
18
21
19
22
// Main
20
23
···
32
35
current_route: Route,
33
36
posts: List(posts.Post(Msg)),
34
37
projects: List(projects.Project(Msg)),
35
-
repositories: List(Repository)
38
+
repos: List(Repository),
36
39
)
37
40
}
38
41
···
62
65
description: String,
63
66
name: String,
64
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,
65
81
)
66
82
}
67
83
68
84
fn get_repositories() -> Effect(Msg) {
69
85
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
-
))
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
+
))
77
96
lustre_http.get(url, lustre_http.expect_json(decoder, ApiGotRepositories))
78
97
}
79
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
+
80
123
fn switch_theme() -> Effect(Msg) {
81
124
effect.from(fn(disp) {
82
125
do_switch_theme()
···
101
144
current_route: get_route(),
102
145
projects: projects.all(),
103
146
posts: posts.all(),
147
+
repos: [],
104
148
),
105
149
{
106
150
init_theme()
107
-
modem.init(on_route_change)
151
+
effect.batch([get_repositories(), modem.init(on_route_change)])
108
152
},
109
153
)
110
154
}
···
136
180
)
137
181
ChangeDarkMode -> #(model, switch_theme())
138
182
DoneChangeDarkMode -> #(model, effect.none())
139
-
ApiGotRepositories -> #(model, effect.none())
183
+
ApiGotRepositories(Ok(repos)) -> #(
184
+
Model(..model, repos: repos),
185
+
effect.none(),
186
+
)
187
+
188
+
ApiGotRepositories(Error(_)) -> #(model, effect.none())
140
189
}
141
190
}
142
191
···
219
268
])
220
269
}
221
270
222
-
fn view_home(_model: Model) -> Element(Msg) {
271
+
fn view_home(model: Model) -> Element(Msg) {
223
272
html.div([], [
224
273
html.div(
225
274
[
···
229
278
],
230
279
[
231
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
+
]),
232
284
element.text(
233
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
234
286
to begin other projects like ",
···
247
299
),
248
300
cluster.of(
249
301
html.div,
250
-
[attr.class("gap-4 sm:gap-20 flex flex-col sm:flex-row")],
302
+
[attr.class("gap-4 sm:gap-20 flex flex-col md:flex-row")],
251
303
[
252
304
ui.stack([], [
253
305
html.h3([attr.class("text-2xl underline")], [
···
296
348
"drop-shadow-md text-xl p-4 mb-4 rounded-xl bg-gray-200 dark:bg-neutral-800 dark:text-neutral-200",
297
349
),
298
350
],
299
-
[html.h1([attr.class("text-3xl m-0 text-pink-400")],[element.text("Repositories")])],
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
+
],
300
402
),
301
403
])
302
404
}