···11-//// Lumina > Client
11+//// Lumina > Web-end
22//// Main entry point for Lumina's web frontend. This module contains all side-effects, the update function. Lustre initialisation and more.
33////
44//// It'll also contain one of the two update() function implementations, the websocket one. Since the Gleam backend
···22222323// Imports ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
24242525-import gleam/option.{None}
2626-import gleam/result
2727-import gleam/string
2828-import lumina_client/model_type.{
2929- type Model, type Msg as Message, type Route, Model,
3030-}
2525+import lumina_client/message.{type Message}
2626+import lumina_client/model_type
3127import lumina_client/view
3232-import lustre
3328import lustre/effect
3429import off_topic
35303631// Entrypoints ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3737-/// Entry point for mainly the Rust backend, which doesn't use server-components and does a server-client based
3838-/// approach instead.
3939-pub fn main() -> Result(lustre.Runtime(Message), lustre.Error) {
4040- // This module was a mess, and the lustre_websocket package is outdated.
4141- // Good reason for me to throw it all out amidst a refactor!
4242- // - Mar
4343- // let assert Ok(_) = lustre.start(app(api_based_updates), "#app", Nil)
4444- todo as "The api wrapper should kick in here."
4545-}
46324747-/// Main entry of the lumina_client, meant to be consumed as a server component
4848-pub fn app() {
4949- off_topic.component(
5050- init:,
5151- update:,
5252- view: view.view,
5353- subscriptions:,
5454- options: [],
5555- )
3333+pub fn main() {
3434+ panic as "Currently, running the frontend as a js bundle is unsupported, as focus is on server components."
5635}
57365858-// Common functionality ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5959-6060-/// Init function
6161-fn init(_) -> #(Model, effect.Effect(Message)) {
6262- let initial_route = model_type.Landing
6363- #(
6464- Model(page: initial_route, user: None, token: None, status: Ok(Nil)),
6565- effect.none(),
6666- )
6767- |> echo as "Init output"
3737+/// Entrypoint to interface directly, usable in server components
3838+pub fn app(
3939+ update: fn(model_type.Model, Message) ->
4040+ #(model_type.Model, effect.Effect(Message)),
4141+) {
4242+ off_topic.application(init:, update:, view: view.view, subscriptions:)
6843}
69447070-// Event handling ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
7171-7272-/// Subscriptions
7373-fn subscriptions(_model: Model) -> off_topic.Subscription(Message) {
7474- off_topic.batch([])
4545+fn subscriptions(_model: model_type.Model) -> off_topic.Subscription(Message) {
4646+ // for now
4747+ off_topic.none()
7548}
76497777-/// Update function used mainly with the Rust backend
7878-fn update(model: Model, message: Message) -> #(Model, effect.Effect(Message)) {
7979- echo message as "Received message"
8080- case message {
8181- model_type.UserNavigatedToLoginPage -> #(
8282- Model(
8383- ..model,
8484- page: model_type.Login(
8585- fields: model_type.LoginFields("", ""),
8686- success: None,
8787- ),
8888- ),
8989- effect.none(),
9090- )
9191- model_type.UserNavigatedToRegisterPage -> #(
9292- Model(
9393- ..model,
9494- page: model_type.Register(
9595- fields: model_type.RegisterPageFields("", "", "", ""),
9696- ready: None,
9797- ),
9898- ),
9999- effect.none(),
100100- )
101101- model_type.UserNavigatedToLandingPage -> #(
102102- Model(..model, page: model_type.Landing),
103103- effect.none(),
104104- )
105105- model_type.UserUpdatedControlledEmailField(new_email) -> {
106106- case model.page {
107107- model_type.Register(fields, ready) -> #(
108108- Model(
109109- ..model,
110110- page: model_type.Register(
111111- fields: model_type.RegisterPageFields(
112112- ..fields,
113113- emailfield: new_email,
114114- ),
115115- ready:,
116116- ),
117117- ),
118118- {
119119- // This block emits an effect to send RegisterPrecheck message to the server
120120- todo as "RegisterPrecheck(
121121- fields.emailfield,
122122- fields.usernamefield,
123123- fields.passwordfield,
124124- )"
125125- },
126126- )
127127- model_type.Login(fields, _) -> #(
128128- Model(
129129- ..model,
130130- page: model_type.Login(
131131- fields: model_type.LoginFields(..fields, emailfield: new_email),
132132- success: None,
133133- ),
134134- ),
135135- effect.none(),
136136- )
137137- _ -> #(model, effect.none())
138138- }
139139- }
140140- model_type.UserUpdatedControlledPasswordField(new_password) -> {
141141- case model.page {
142142- model_type.Register(fields, ready) -> #(
143143- Model(
144144- ..model,
145145- page: model_type.Register(
146146- model_type.RegisterPageFields(
147147- ..fields,
148148- passwordfield: new_password,
149149- ),
150150- ready:,
151151- ),
152152- ),
153153- {
154154- // This block emits an effect to send RegisterPrecheck message to the server
155155- todo as "RegisterPrecheck(
156156- fields.emailfield,
157157- fields.usernamefield,
158158- fields.passwordfield,
159159- )"
160160- },
161161- )
162162- model_type.Login(fields, _success) -> {
163163- let username_email = case string.starts_with(fields.emailfield, "@") {
164164- True -> string.drop_start(fields.emailfield, 1)
165165- False -> fields.emailfield
166166- }
167167- let new_username_email = case string.contains(username_email, "@") {
168168- True -> {
169169- // Is an email, what now!
170170- username_email
171171- }
172172- False -> {
173173- string.trim(username_email)
174174- |> string.replace(" ", "")
175175- |> string.lowercase()
176176- |> string.replace("@", "")
177177- |> string.replace(".", "")
178178- }
179179- }
180180- #(
181181- Model(
182182- ..model,
183183- page: model_type.Login(
184184- fields: model_type.LoginFields(
185185- passwordfield: new_password,
186186- emailfield: new_username_email,
187187- ),
188188- success: None,
189189- ),
190190- ),
191191- effect.none(),
192192- )
193193- }
194194- _ -> #(model, effect.none())
195195- }
196196- }
197197- model_type.UserUpdatedControlledPasswordConfirmField(
198198- new_password_confirmation,
199199- ) -> {
200200- case model.page {
201201- model_type.Register(fields, ready) -> #(
202202- Model(
203203- ..model,
204204- page: model_type.Register(
205205- fields: model_type.RegisterPageFields(
206206- ..fields,
207207- passwordconfirmfield: new_password_confirmation,
208208- ),
209209- ready:,
210210- ),
211211- ),
212212- {
213213- // This block emits an effect to send RegisterPrecheck message to the server
214214- todo as "RegisterPrecheck(
215215- fields.emailfield,
216216- fields.usernamefield,
217217- fields.passwordfield,
218218- )"
219219- },
220220- )
221221- _ -> #(model, effect.none())
222222- }
223223- }
224224- model_type.UserUpdatedControlledUsernameField(new_username) -> {
225225- case model.page {
226226- model_type.Register(fields, ready) -> #(
227227- Model(
228228- ..model,
229229- page: model_type.Register(
230230- fields: model_type.RegisterPageFields(..fields, usernamefield: {
231231- case string.starts_with(new_username, "@") {
232232- True -> string.drop_start(new_username, 1)
233233- False -> new_username
234234- }
235235- |> string.trim()
236236- |> string.replace(" ", "")
237237- |> string.lowercase()
238238- |> string.replace("@", "")
239239- |> string.replace(".", "")
240240- }),
241241- ready:,
242242- ),
243243- ),
244244- {
245245- todo as "RegisterPrecheck(
246246- fields.emailfield,
247247- fields.usernamefield,
248248- fields.passwordfield,
249249- )"
250250- },
251251- )
252252- _ -> #(model, effect.none())
253253- }
254254- }
255255- model_type.EmailFieldLostFocus -> {
256256- // This handles the login username/email field value once the user seems to be done typing.
257257- let assert model_type.Login(fields, _success) = model.page
258258- let value = case string.starts_with(fields.emailfield, "@") {
259259- True -> string.drop_start(fields.emailfield, 1)
260260- False -> fields.emailfield
261261- }
262262- let new_value = case string.contains(value, "@") {
263263- True -> {
264264- // Is an email, what now!
265265- value
266266- }
267267- False -> {
268268- string.trim(value)
269269- |> string.replace(" ", "")
270270- |> string.lowercase()
271271- |> string.replace("@", "")
272272- |> string.replace(".", "")
273273- }
274274- }
275275- #(
276276- Model(
277277- ..model,
278278- page: model_type.Login(
279279- fields: model_type.LoginFields(..fields, emailfield: new_value),
280280- success: None,
281281- ),
282282- ),
283283- effect.none(),
284284- )
285285- }
286286- model_type.UserClickedLogout -> todo as "session should be destroyed here"
287287- model_type.UserSubmittedLogin(_) -> {
288288- // let assert model_type.Login(fields, _) = model.page
289289- todo as "LoginAuthenticationRequest(
290290- fields.emailfield,
291291- fields.passwordfield,
292292- )"
293293- }
294294- model_type.UserSubmittedSignup(_) -> {
295295- let assert model_type.Register(fields, ready) = model.page
5050+// Common functionality ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
29651297297- case
298298- {
299299- { ready |> option.is_some() }
300300- && { ready |> option.unwrap(Error("")) |> result.is_ok() }
301301- && { fields.passwordfield == fields.passwordconfirmfield }
302302- }
303303- {
304304- True -> {
305305- // console.log("Submitting signup form")
306306- let effect =
307307- todo as "RegisterRequest(
308308- fields.emailfield,
309309- fields.usernamefield,
310310- fields.passwordfield,
311311- )"
312312- #(model, effect)
313313- }
314314- False -> {
315315- // console.error("Form not ready to submit")
316316- #(model, effect.none())
317317- }
318318- }
319319- }
320320- model_type.UserSwitchedTimeLineTo(tid) -> todo
321321-322322- model_type.LoadMorePosts(timeline_name) -> todo
323323-324324- model_type.UserClosedModal -> todo
325325-326326- model_type.StartDraggingModalBox(x, y) -> todo
327327-328328- model_type.MoveModalBoxTo(x, y) -> todo
329329-330330- model_type.UpdateLastRefreshRequestTime(_) -> todo
331331- model_type.ModemChangePage(_) -> todo
332332- model_type.SetModal(_) -> todo
333333- }
5252+/// Init function
5353+/// Inherits effects, because these depend on where the init is ran from.
5454+fn init(_) -> #(model_type.Model, effect.Effect(Message)) {
5555+ todo
33456}
+20
web/src/lumina_client/message.gleam
···11+//// Lumina > Web-end > Messages
22+33+// Lumina/Peonies
44+// Copyright (C) 2018-2026 MLC 'Strawmelonjuice' Bloeiman and contributors.
55+//
66+// This software is licensed under the European Union Public Licence (EUPL) v1.2.
77+// You may not use this work except in compliance with the Licence.
88+// You may obtain a copy of the Licence at: https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
99+//
1010+// AI TRAINING NOTICE: Rights for TDM and AI training are EXPRESSLY RESERVED
1111+// under Art 4(3) Dir 2019/790. AI training constitutes a Derivative Work.
1212+// See LICENSE file in the repository root for full details.
1313+//
1414+//
1515+// This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND.
1616+// See the Licence for the specific language governing permissions and limitations.
1717+1818+pub type Message {
1919+ OldMessage(String)
2020+}
+17
web/src/lumina_client/model.gleam
···11+//// Lumina > Client > Model
22+//// Lumina's model is the central source of truth for the client application state.
33+44+// Lumina/Peonies
55+// Copyright (C) 2018-2026 MLC 'Strawmelonjuice' Bloeiman and contributors.
66+//
77+// This software is licensed under the European Union Public Licence (EUPL) v1.2.
88+// You may not use this work except in compliance with the Licence.
99+// You may obtain a copy of the Licence at: https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
1010+//
1111+// AI TRAINING NOTICE: Rights for TDM and AI training are EXPRESSLY RESERVED
1212+// under Art 4(3) Dir 2019/790. AI training constitutes a Derivative Work.
1313+// See LICENSE file in the repository root for full details.
1414+//
1515+//
1616+// This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND.
1717+// See the Licence for the specific language governing permissions and limitations.
+14
web/src/lumina_client/model_type.gleam
···11//// Lumina > Client > Model
22//// Lumina's model is the central source of truth for the client application state.
33+////
44+//// This module is to be replaced by the newer and more targeted model.gleam module.
3546// Lumina/Peonies
57// Copyright (C) 2018-2026 MLC 'Strawmelonjuice' Bloeiman and contributors.
···1517//
1618// This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND.
1719// See the Licence for the specific language governing permissions and limitations.
2020+pub fn from_itr2_message(
2121+ from: element.Element(Msg),
2222+) -> element.Element(message.Message) {
2323+ element.map(from, fn(original_message: Msg) -> message.Message {
2424+ case original_message {
2525+ _ -> message.OldMessage(string.inspect(original_message))
2626+ }
2727+ })
2828+}
18291930import gleam/dict.{type Dict}
2031import gleam/dynamic/decode
2132import gleam/json
2233import gleam/list
2334import gleam/option.{type Option, None, Some}
3535+import gleam/string
2436import gleam/uri.{type Uri}
3737+import lumina_client/message
3838+import lustre/element
25392640pub type Msg {
2741 UpdateLastRefreshRequestTime(Int)
+14-5
web/src/lumina_client/view.gleam
···2424import lumina_client/helpers.{
2525 get_color_scheme, login_view_checker, model_local_storage_key,
2626}
2727+import lumina_client/message
2728import lumina_client/model_type.{
2829 type Model, type Msg, HomeTimeline, Landing, Licence, Login, NotFound,
2930 Register, UserNavigatedToLandingPage, UserNavigatedToLoginPage,
···3940import lustre/element/html
4041import lustre/event
41424242-pub fn view(model: Model) -> Element(model_type.Msg) {
4343+pub fn view(model: Model) -> Element(message.Message) {
4344 case model.page {
4444- Landing -> view_landing()
4545- Register(..) -> view_register(model)
4646- Login(..) -> view_login(model)
4747- HomeTimeline(..) -> view_homepage(model)
4545+ Landing ->
4646+ view_landing()
4747+ |> model_type.from_itr2_message
4848+ Register(..) ->
4949+ view_register(model)
5050+ |> model_type.from_itr2_message
5151+ Login(..) ->
5252+ view_login(model)
5353+ |> model_type.from_itr2_message
5454+ HomeTimeline(..) ->
5555+ view_homepage(model)
5656+ |> model_type.from_itr2_message
4857 NotFound(uri:) -> todo as "No 404 page yet."
4958 Licence ->
5059 todo as "Licence should be shown by the client if it's not shown by the server."