Compare commits
4 Commits
17dec803a2
...
69457f37e3
Author | SHA1 | Date | |
---|---|---|---|
69457f37e3 | |||
b2369c6073 | |||
b90590d3b5 | |||
811d29b9ec |
124
Cargo.lock
generated
124
Cargo.lock
generated
@@ -53,6 +53,17 @@ version = "1.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457"
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.88"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atoi"
|
||||
version = "2.0.0"
|
||||
@@ -145,6 +156,22 @@ dependencies = [
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-messages"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d67ce6e7bc1e1e71f2a4e86d418045a29c63c4ebb631f3d9bb2f81c4958ea391"
|
||||
dependencies = [
|
||||
"axum-core",
|
||||
"http",
|
||||
"parking_lot",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tower",
|
||||
"tower-sessions-core",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.75"
|
||||
@@ -381,6 +408,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e"
|
||||
dependencies = [
|
||||
"powerfmt",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -481,6 +509,20 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.31"
|
||||
@@ -525,6 +567,17 @@ version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.31"
|
||||
@@ -545,6 +598,7 @@ checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-macro",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"memchr",
|
||||
@@ -1007,6 +1061,7 @@ checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"scopeguard",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2135,6 +2190,22 @@ dependencies = [
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-cookies"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "151b5a3e3c45df17466454bb74e9ecedecc955269bdedbf4d150dfa393b55a36"
|
||||
dependencies = [
|
||||
"axum-core",
|
||||
"cookie",
|
||||
"futures-util",
|
||||
"http",
|
||||
"parking_lot",
|
||||
"pin-project-lite",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-http"
|
||||
version = "0.6.6"
|
||||
@@ -2173,6 +2244,57 @@ version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
|
||||
|
||||
[[package]]
|
||||
name = "tower-sessions"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43a05911f23e8fae446005fe9b7b97e66d95b6db589dc1c4d59f6a2d4d4927d3"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"http",
|
||||
"time",
|
||||
"tokio",
|
||||
"tower-cookies",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tower-sessions-core",
|
||||
"tower-sessions-memory-store",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-sessions-core"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce8cce604865576b7751b7a6bc3058f754569a60d689328bb74c52b1d87e355b"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum-core",
|
||||
"base64",
|
||||
"futures",
|
||||
"http",
|
||||
"parking_lot",
|
||||
"rand",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
"time",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-sessions-memory-store"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb05909f2e1420135a831dd5df9f5596d69196d0a64c3499ca474c4bd3d33242"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"time",
|
||||
"tokio",
|
||||
"tower-sessions-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.41"
|
||||
@@ -2421,6 +2543,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"axum-extra",
|
||||
"axum-messages",
|
||||
"chrono",
|
||||
"maud",
|
||||
"rust-i18n",
|
||||
@@ -2428,6 +2551,7 @@ dependencies = [
|
||||
"sqlx",
|
||||
"tokio",
|
||||
"tower-http",
|
||||
"tower-sessions",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
|
@@ -6,6 +6,7 @@ edition = "2024"
|
||||
[dependencies]
|
||||
axum = "0.8"
|
||||
axum-extra = { version = "0.10", features = ["cookie"] }
|
||||
axum-messages = "0.8.0"
|
||||
chrono = { version = "0.4.41", features = ["serde"] }
|
||||
maud = { version = "0.27", features = ["axum"] }
|
||||
rust-i18n = "3.1.5"
|
||||
@@ -13,4 +14,5 @@ serde = { version = "1", features = ["derive"] }
|
||||
sqlx = { version = "0.8", features = ["runtime-tokio-rustls", "sqlite", "macros", "chrono"] }
|
||||
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
|
||||
tower-http = { version = "0.6", features = ["fs"] }
|
||||
tower-sessions = "0.14.0"
|
||||
uuid = { version = "1.17", features = ["v4", "serde"] }
|
||||
|
@@ -1,2 +1,8 @@
|
||||
_version: 1
|
||||
digital_shadows: "Digitaler Schatten"
|
||||
icon_home: "🏠"
|
||||
icon_camera: "📸"
|
||||
found_camera_title: "Kamera '%{name}' gefunden"
|
||||
found_camera_body: "✨ You are a star ✨ Star dust sprinkle, sprinkle."
|
||||
ask_to_change_name: "%{name}, do you want to be named something different? No worries, change here 👇"
|
||||
funny_name_change_placeholder: "✨ Your new name starts here ✨"
|
||||
|
@@ -1,2 +1,8 @@
|
||||
_version: 1
|
||||
digital_shadows: "Digital Shadows"
|
||||
icon_home: "🏠"
|
||||
icon_camera: "📸"
|
||||
found_camera_title: "Camera '%{name}' found"
|
||||
found_camera_body: "✨ You are a star ✨ Star dust sprinkle, sprinkle."
|
||||
ask_to_change_name: "%{name}, do you want to be named something different? No worries, change here 👇"
|
||||
funny_name_change_placeholder: "✨ Your new name starts here ✨"
|
||||
|
125
src/game.rs
125
src/game.rs
@@ -1,4 +1,4 @@
|
||||
use crate::{language::language, page::new, Backend};
|
||||
use crate::{language::language, page::Page, Backend, NameUpdateError};
|
||||
use axum::{
|
||||
extract::{Path, State},
|
||||
http::HeaderMap,
|
||||
@@ -7,6 +7,7 @@ use axum::{
|
||||
Form, Router,
|
||||
};
|
||||
use axum_extra::extract::CookieJar;
|
||||
use axum_messages::Messages;
|
||||
use maud::{html, Markup, PreEscaped};
|
||||
use serde::Deserialize;
|
||||
use std::sync::Arc;
|
||||
@@ -15,6 +16,7 @@ use uuid::Uuid;
|
||||
async fn index(
|
||||
State(backend): State<Arc<Backend>>,
|
||||
cookies: CookieJar,
|
||||
messages: Messages,
|
||||
headers: HeaderMap,
|
||||
) -> Response {
|
||||
let (cookies, req) = backend.client_full(cookies, &headers).await;
|
||||
@@ -24,66 +26,61 @@ async fn index(
|
||||
let amount_total_cameras = backend.amount_total_cameras().await;
|
||||
let highscore = backend.highscore().await;
|
||||
|
||||
let markup = new(
|
||||
html! {
|
||||
hgroup {
|
||||
h1 { "Who finds the most cameras?" }
|
||||
}
|
||||
p {
|
||||
mark { "TODO: Explanation of AEF / digital shadows / search game" }
|
||||
}
|
||||
let mut page = Page::new(req.lang);
|
||||
page.messages(messages);
|
||||
let markup = page.content(html! {
|
||||
hgroup {
|
||||
h1 { "Who finds the most cameras?" }
|
||||
}
|
||||
p {
|
||||
mark { "TODO: Explanation of AEF / digital shadows / search game" }
|
||||
}
|
||||
|
||||
div.mb-sm {
|
||||
(client.get_display_name())
|
||||
", do you want to be named something different? No worries, change here 👇"
|
||||
}
|
||||
div.mb-sm { (t!("ask_to_change_name", name = client.get_display_name())) }
|
||||
|
||||
form action="/name" method="post" {
|
||||
fieldset role="group" {
|
||||
input
|
||||
name="name"
|
||||
placeholder="✨ Your new name starts here ✨"
|
||||
aria-label="Name";
|
||||
input type="submit" value="Save";
|
||||
}
|
||||
form action="/name" method="post" {
|
||||
fieldset role="group" {
|
||||
input name="name" placeholder=(t!("funny_name_change_placeholder")) aria-label="Name";
|
||||
input type="submit" value="Save";
|
||||
}
|
||||
}
|
||||
|
||||
p.mb-0 {
|
||||
"You have found "
|
||||
(sightings.len())
|
||||
"/"
|
||||
(amount_total_cameras)
|
||||
" cameras:"
|
||||
progress value=(sightings.len()) max=(amount_total_cameras);
|
||||
p.mb-0 {
|
||||
"You have found "
|
||||
(sightings.len())
|
||||
"/"
|
||||
(amount_total_cameras)
|
||||
" cameras:"
|
||||
progress value=(sightings.len()) max=(amount_total_cameras);
|
||||
}
|
||||
|
||||
ol.flex.small {
|
||||
@for sighting in &sightings {
|
||||
li { (sighting.camera.name) }
|
||||
}
|
||||
}
|
||||
|
||||
ol.flex.small {
|
||||
@for (idx, sighting) in sightings.iter().enumerate() {
|
||||
li {
|
||||
(sighting.camera.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
h2 { "Highscore" }
|
||||
ul.iterated {
|
||||
@for rank in highscore {
|
||||
li.card {
|
||||
span {
|
||||
span.font-headline.rank.text-muted { (rank.rank) "." }
|
||||
@if rank.client == client { (PreEscaped("<mark id='ranking'>")) }
|
||||
(rank.client.get_display_name())
|
||||
@if rank.client == client { (PreEscaped("</mark>")) }
|
||||
}
|
||||
span.font-headline.font-lg { (rank.amount) (PreEscaped(" ")) "📸" }
|
||||
p {
|
||||
h2 { "Highscore" }
|
||||
ul.iterated {
|
||||
@for rank in highscore {
|
||||
li.card {
|
||||
span {
|
||||
span.font-headline.rank.text-muted { (rank.rank) "." }
|
||||
@if rank.client == client { (PreEscaped("<mark id='ranking'>")) }
|
||||
(rank.client.get_display_name())
|
||||
@if rank.client == client { (PreEscaped("</mark>")) }
|
||||
}
|
||||
span.font-headline.font-lg {
|
||||
(rank.amount)
|
||||
(PreEscaped(" "))
|
||||
(t!("icon_camera"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
req.lang,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
(cookies, markup).into_response()
|
||||
}
|
||||
@@ -92,6 +89,7 @@ async fn game(
|
||||
State(backend): State<Arc<Backend>>,
|
||||
cookies: CookieJar,
|
||||
headers: HeaderMap,
|
||||
messages: Messages,
|
||||
Path(uuid): Path<String>,
|
||||
) -> Result<Redirect, Response> {
|
||||
let (cookies, client) = backend.client(cookies).await;
|
||||
@@ -104,20 +102,20 @@ async fn game(
|
||||
return Err(not_found(cookies, headers).await.into_response());
|
||||
};
|
||||
|
||||
let succ = backend.client_found_camera(&client, &camera).await;
|
||||
// TODO: show succ/err based on succ
|
||||
if backend.client_found_camera(&client, &camera).await {
|
||||
messages.info(format!("found-cam|{}", camera.name));
|
||||
} else {
|
||||
messages.info("err|Try to find a new camera!|You have already collected this camera.|");
|
||||
}
|
||||
|
||||
Ok(Redirect::to("/game"))
|
||||
}
|
||||
|
||||
async fn not_found(cookies: CookieJar, headers: HeaderMap) -> Markup {
|
||||
let lang = language(&cookies, &headers);
|
||||
new(
|
||||
html! {
|
||||
h1 { "uups" }
|
||||
},
|
||||
lang,
|
||||
)
|
||||
Page::new(lang).content(html! {
|
||||
h1 { "uups" }
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@@ -128,13 +126,16 @@ struct NameForm {
|
||||
async fn set_name(
|
||||
State(backend): State<Arc<Backend>>,
|
||||
cookies: CookieJar,
|
||||
messages: Messages,
|
||||
Form(form): Form<NameForm>,
|
||||
) -> Response {
|
||||
let (cookies, client) = backend.client(cookies).await;
|
||||
|
||||
// Update the client's name in the backend
|
||||
// TODO: handle succ/err msg
|
||||
let _ = backend.set_client_name(&client, &form.name).await;
|
||||
match backend.set_client_name(&client, &form.name).await {
|
||||
Ok(()) => messages.info("set-name-succ"),
|
||||
Err(NameUpdateError::TooShort(expected, actual)) => messages.info(format!("err|That's too little!|We need more information about you. Give us at least {expected} characters for you new name!|Received characters: {actual}")),
|
||||
Err(NameUpdateError::TooLong(expected, actual)) => messages.info(format!("err|That's too much!|We only live in (20)25, so please use less than {expected} characters for your new name.|Received characters: {actual}")),
|
||||
};
|
||||
|
||||
// Redirect back to the game page
|
||||
(cookies, Redirect::to("/game")).into_response()
|
||||
|
50
src/index.rs
50
src/index.rs
@@ -8,10 +8,7 @@ pub(super) async fn index(cookies: CookieJar, headers: HeaderMap) -> Markup {
|
||||
|
||||
rust_i18n::set_locale(lang.to_locale());
|
||||
|
||||
let mut page = Page::new(lang);
|
||||
page.succ("Yeah! That worked!".into());
|
||||
page.err("Damn...".into());
|
||||
|
||||
let page = Page::new(lang);
|
||||
page.content(
|
||||
html! {
|
||||
h1 { (t!("digital_shadows")) }
|
||||
@@ -27,9 +24,7 @@ pub(super) async fn index(cookies: CookieJar, headers: HeaderMap) -> Markup {
|
||||
}
|
||||
p {
|
||||
"Artists: René Mayrhofer (AT), Philipp Hofer (AT), Laura Poulbot (FR), Airan Berg (AT), Andrea Hummer (AT), Ilona Roth (DE/AT), Linda Huber (AT), Gisela Klammsteiner (AT), Sara Koniarek (AT), Simon Sharkey (GB), Valerio Iurato (IT), Doris Roth (DE), Alina Lugovskaya (UA/RU), Selina Nowak (AT), JeanClaude Grieco (AR/AT), Florian Böttcher (AT), Ethem Saygieder-Fischer (AT)"
|
||||
span.easteregg {
|
||||
", Marie Birner (Couch)"
|
||||
}
|
||||
span.easteregg { ", Marie Birner (Couch)" }
|
||||
}
|
||||
blockquote {
|
||||
"Digital Shadows confronts visitors with their digital self – copied, measured, analyzed. An experiment on data power, visibility, and control in the digital age."
|
||||
@@ -41,41 +36,32 @@ pub(super) async fn index(cookies: CookieJar, headers: HeaderMap) -> Markup {
|
||||
"Digital Shadows invites the participants to experience questions of digital and physical identity, data security, and control. In immersive zones woven with choreographic elements, visitors encounter themselves mirrored, copied, measured and simultaneously lose themselves in a system that knows more about them than they intend to reveal. Between play and analysis, concealment and transparency, a reflection emerges on identity in the age of facial recognition, deepfakes, and algorithmic profiling. How does one fool a camera? How visible do I want to be? Who owns what I leave behind, and who profits from it? This experiment is a collaborative endeavor between science and art, making power, visibility, and self-determination in digital space tangible. Through an exploration of digital materiality and algorithmic intelligence, a sometimes absurd, always immediate reflection unfolds on our role in data-driven worlds until we face our digital dilemma, and the choice is still ours to make."
|
||||
}
|
||||
|
||||
h2 {
|
||||
"What to do with this information?"
|
||||
}
|
||||
h2 { "What to do with this information?" }
|
||||
|
||||
div.grid.gap-lg {
|
||||
article {
|
||||
header {
|
||||
"Visit our booth"
|
||||
}
|
||||
article {
|
||||
header { "Visit our booth" }
|
||||
|
||||
"We will be delighted to see you at our booth in the Post City Linz, where our team will show you, what happens when your Digital Shadow becomes allmighty. "
|
||||
"We will be delighted to see you at our booth in the Post City Linz, where our team will show you, what happens when your Digital Shadow becomes allmighty. "
|
||||
|
||||
a href="https://www.jku.at/ars-electronica-2025-panic-yes-no/digital-shadows/" target="_blank" title="Go to JKU Information Page" {
|
||||
"Find out more"
|
||||
}
|
||||
a
|
||||
href="https://www.jku.at/ars-electronica-2025-panic-yes-no/digital-shadows/"
|
||||
target="_blank"
|
||||
title="Go to JKU Information Page" {
|
||||
"Find out more"
|
||||
}
|
||||
|
||||
footer {
|
||||
"Where: Postcity Linz"
|
||||
}
|
||||
}
|
||||
article {
|
||||
header {
|
||||
"Play our game"
|
||||
footer { "Where: Postcity Linz" }
|
||||
}
|
||||
article {
|
||||
header { "Play our game" }
|
||||
|
||||
"Ever wandered through Linz with the aim to find (hidden) cameras? Well, if you are that kind of person than our 'Discover cameras' game will be ideal for you! "
|
||||
"Ever wandered through Linz with the aim to find (hidden) cameras? Well, if you are that kind of person than our 'Discover cameras' game will be ideal for you! "
|
||||
|
||||
a href="/game" title="Go to Game Page" {
|
||||
"Find out more"
|
||||
}
|
||||
a href="/game" title="Go to Game Page" { "Find out more" }
|
||||
|
||||
footer {
|
||||
"Where: all over Linz"
|
||||
footer { "Where: all over Linz" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
20
src/main.rs
20
src/main.rs
@@ -1,9 +1,11 @@
|
||||
use crate::model::client::Client;
|
||||
use axum::{http::HeaderMap, routing::get, Router};
|
||||
use axum_extra::extract::{cookie::Cookie, CookieJar};
|
||||
use axum_messages::MessagesManagerLayer;
|
||||
use sqlx::{pool::PoolOptions, sqlite::SqliteConnectOptions, SqlitePool};
|
||||
use std::{fmt::Display, str::FromStr, sync::Arc};
|
||||
use tower_http::services::ServeDir;
|
||||
use tower_sessions::{MemoryStore, SessionManagerLayer};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[macro_use]
|
||||
@@ -68,6 +70,11 @@ struct Req {
|
||||
lang: Language,
|
||||
}
|
||||
|
||||
pub(crate) enum NameUpdateError {
|
||||
TooLong(usize, usize),
|
||||
TooShort(usize, usize),
|
||||
}
|
||||
|
||||
impl Backend {
|
||||
async fn client(&self, cookies: CookieJar) -> (CookieJar, Client) {
|
||||
let existing_uuid = cookies
|
||||
@@ -91,12 +98,12 @@ impl Backend {
|
||||
(cookies, Req { client, lang })
|
||||
}
|
||||
|
||||
async fn set_client_name(&self, client: &Client, name: &str) -> Result<(), String> {
|
||||
async fn set_client_name(&self, client: &Client, name: &str) -> Result<(), NameUpdateError> {
|
||||
if name.len() > 25 {
|
||||
return Err("Maximum 25 chars are allowed".into());
|
||||
return Err(NameUpdateError::TooLong(25, name.len()));
|
||||
}
|
||||
if name.len() < 3 {
|
||||
return Err("Minimum of 3 chars needed".into());
|
||||
return Err(NameUpdateError::TooShort(3, name.len()));
|
||||
}
|
||||
|
||||
match self {
|
||||
@@ -118,6 +125,9 @@ impl Backend {
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let session_store = MemoryStore::default();
|
||||
let session_layer = SessionManagerLayer::new(session_store).with_secure(false);
|
||||
|
||||
let connection_options = SqliteConnectOptions::from_str("sqlite://db.sqlite").unwrap();
|
||||
let db: SqlitePool = PoolOptions::new()
|
||||
.connect_with(connection_options)
|
||||
@@ -128,7 +138,9 @@ async fn main() {
|
||||
.route("/", get(index::index))
|
||||
.nest_service("/static", ServeDir::new("./static/serve"))
|
||||
.merge(game::routes())
|
||||
.with_state(Arc::new(Backend::Sqlite(db)));
|
||||
.with_state(Arc::new(Backend::Sqlite(db)))
|
||||
.layer(MessagesManagerLayer)
|
||||
.layer(session_layer);
|
||||
|
||||
// run our app with hyper, listening globally on port 3000
|
||||
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
|
||||
|
196
src/page.rs
196
src/page.rs
@@ -1,135 +1,137 @@
|
||||
use crate::Language;
|
||||
use axum_messages::Messages;
|
||||
use maud::{html, Markup, DOCTYPE};
|
||||
|
||||
// TODO: set dynamic meta lang attribute
|
||||
|
||||
// TODO: remove function
|
||||
#[deprecated]
|
||||
pub fn new(content: Markup, lang: Language) -> Markup {
|
||||
let page = Page::new(lang);
|
||||
page.content(content)
|
||||
}
|
||||
|
||||
pub(crate) struct Page {
|
||||
lang: Language,
|
||||
succ: Option<String>,
|
||||
err: Option<String>,
|
||||
found_camera: Option<String>,
|
||||
new_name: bool,
|
||||
err: Option<(String, String, String)>,
|
||||
}
|
||||
|
||||
impl Page {
|
||||
pub fn new(lang: Language) -> Self {
|
||||
Self {
|
||||
lang,
|
||||
succ: None,
|
||||
found_camera: None,
|
||||
new_name: false,
|
||||
err: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn succ(&mut self, msg: String) {
|
||||
self.succ = Some(msg);
|
||||
}
|
||||
|
||||
pub fn err(&mut self, msg: String) {
|
||||
self.err = Some(msg);
|
||||
}
|
||||
|
||||
fn has_msg(&self) -> bool {
|
||||
self.succ.is_some() || self.err.is_some()
|
||||
pub fn messages(&mut self, messages: Messages) {
|
||||
for message in messages {
|
||||
let text = &message.to_string()[..];
|
||||
match (message.level, text) {
|
||||
(_, "set-name-succ") => {
|
||||
self.new_name = true;
|
||||
}
|
||||
(_, msg) if msg.starts_with("found-cam|") => {
|
||||
let (_, name) = msg.split_once('|').expect("we just checked |");
|
||||
self.found_camera = Some(name.into());
|
||||
}
|
||||
(_, msg) if msg.starts_with("err|") => {
|
||||
let mut parts = msg.splitn(4, '|');
|
||||
let _ = parts.next().expect("just checked |");
|
||||
if let (Some(title), Some(body), Some(footer)) =
|
||||
(parts.next(), parts.next(), parts.next())
|
||||
{
|
||||
self.err = Some((title.into(), body.into(), footer.into()));
|
||||
}
|
||||
}
|
||||
(_, _) => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn content(self, content: Markup) -> Markup {
|
||||
rust_i18n::set_locale(self.lang.to_locale());
|
||||
|
||||
html! {
|
||||
(DOCTYPE)
|
||||
html lang=(self.lang) {
|
||||
head {
|
||||
meta charset="utf-8";
|
||||
meta name="viewport" content="width=device-width, initial-scale=1.0";
|
||||
link rel="stylesheet" href="/static/pico.min.css";
|
||||
link rel="stylesheet" href="/static/style.css";
|
||||
title { "Digital Shadows" }
|
||||
}
|
||||
body {
|
||||
header.container {
|
||||
nav {
|
||||
ul {
|
||||
li {
|
||||
a href="/" {
|
||||
strong { "Digital Shadows" }
|
||||
}
|
||||
}
|
||||
}
|
||||
ul {
|
||||
li {
|
||||
a href="/" {
|
||||
span role="img" aria-label="home" { "🏠" }
|
||||
}
|
||||
}
|
||||
li {
|
||||
a href="/game" {
|
||||
span role="img" aria-label="camera" { "📸" }
|
||||
}
|
||||
}
|
||||
li {
|
||||
span id="theme_switcher" {}
|
||||
}
|
||||
|
||||
li {
|
||||
button id="lang-toggle" lang=(self.lang.next_language()) {
|
||||
(self.lang.next_language())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
head {
|
||||
meta charset="utf-8";
|
||||
meta name="viewport" content="width=device-width, initial-scale=1.0";
|
||||
link rel="stylesheet" href="/static/pico.min.css";
|
||||
link rel="stylesheet" href="/static/style.css";
|
||||
title { "Digital Shadows" }
|
||||
}
|
||||
body {
|
||||
header.container {
|
||||
nav {
|
||||
ul {
|
||||
li {
|
||||
a href="/" {
|
||||
strong { "Digital Shadows" }
|
||||
}
|
||||
}
|
||||
}
|
||||
ul {
|
||||
li {
|
||||
a href="/" {
|
||||
span role="img" aria-label="home" { (t!("icon_home")) }
|
||||
}
|
||||
}
|
||||
li {
|
||||
a href="/game" {
|
||||
span role="img" aria-label="camera" { (t!("icon_camera")) }
|
||||
}
|
||||
}
|
||||
li {
|
||||
span id="theme_switcher" {}
|
||||
}
|
||||
|
||||
main.container {
|
||||
article class="succ w-full" {
|
||||
header {
|
||||
"Camera found"
|
||||
}
|
||||
"✨ You are a star ✨ Star dust sprinkle, sprinkle. "
|
||||
a href="#ranking" {
|
||||
"See your ranking"
|
||||
}
|
||||
footer {
|
||||
"Rear Exit Cam"
|
||||
li {
|
||||
button id="lang-toggle" lang=(self.lang.next_language()) {
|
||||
(self.lang.next_language())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
article class="name w-full" {
|
||||
header {
|
||||
"Camera found"
|
||||
main.container {
|
||||
@if let Some(found_camera) = &self.found_camera {
|
||||
article class="succ w-full" {
|
||||
header { (t!("found_camera", name = found_camera)) }
|
||||
(t!("found_camera_body"))
|
||||
a href="#ranking" { "See your ranking" }
|
||||
footer { (found_camera) }
|
||||
}
|
||||
}
|
||||
"text"
|
||||
footer {
|
||||
"Name of Camera"
|
||||
@if self.new_name {
|
||||
article class="name w-full" {
|
||||
header { "New name!" }
|
||||
"Does it feel much different?"
|
||||
}
|
||||
}
|
||||
@if let Some(err) = &self.err {
|
||||
article class="error w-full" {
|
||||
header { (err.0) }
|
||||
(err.1)
|
||||
footer { (err.2) }
|
||||
}
|
||||
}
|
||||
|
||||
section { (content) }
|
||||
}
|
||||
|
||||
article class="error w-full" {
|
||||
header {
|
||||
"Camera found"
|
||||
}
|
||||
"text"
|
||||
footer {
|
||||
"Name of Camera"
|
||||
footer.container {
|
||||
small {
|
||||
"Footer "
|
||||
mark { "to be completed" }
|
||||
a href="#" { "with links" }
|
||||
" • "
|
||||
a target="_blank" href="https://www.digidow.eu/impressum/" {
|
||||
"Impressum"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
section { (content) }
|
||||
script src="/static/theme.js" {}
|
||||
}
|
||||
|
||||
footer.container {
|
||||
small {
|
||||
"Footer "
|
||||
mark { "to be completed" }
|
||||
a href="#" { "with links" }
|
||||
" • "
|
||||
a target="_blank" href="https://www.digidow.eu/impressum/" { "Impressum" }
|
||||
}
|
||||
}
|
||||
script src="/static/theme.js" {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user