Menu
BifrOSt-Web
publicLatest change 7c2cb56e0351160e3b6c17af94380b79b538c130 - Publish 0.2.2 changelog and refresh release facts by Ólafur Búi Ólafsson
use dioxus::prelude::*;
use serde::{Deserialize, Serialize};
const CSS: Asset = asset!("/public/assets/main.css");
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
struct HomepageContent {
locale: String,
design_contract: String,
meta: MetaContent,
brand: BrandContent,
ui: UiContent,
languages: Vec<LanguageOption>,
navigation: Vec<NavigationItem>,
hero: HeroContent,
release_facts: Vec<Fact>,
experience: ExperienceContent,
build: BuildContent,
safety: SafetyContent,
download: DownloadContent,
footer: FooterContent,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
struct MetaContent {
title: String,
description: String,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
struct BrandContent {
name: String,
home_aria: String,
mark_path: String,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
struct UiContent {
primary_navigation_aria: String,
mobile_navigation_aria: String,
open_navigation: String,
language_control: String,
screenshots_aria: String,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
struct LanguageOption {
code: String,
short_label: String,
label: String,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
struct NavigationItem {
label: String,
href: String,
kind: String,
external: bool,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
struct HeroContent {
origin: String,
headline_line_1: String,
headline_line_2: String,
lede: String,
primary_label: String,
primary_href: String,
secondary_label: String,
secondary_href: String,
image_path: String,
image_alt: String,
marker_top: String,
marker_bottom: String,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
struct Fact {
label: String,
value: String,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
struct ExperienceContent {
heading: String,
intro: String,
image_coordinate: String,
scenes: Vec<SceneContent>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
struct SceneContent {
label: String,
title: String,
note: String,
image_path: String,
image_alt: String,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
struct BuildContent {
heading: String,
intro: String,
layers: Vec<LayerContent>,
profiles_intro: String,
profiles: Vec<String>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
struct LayerContent {
tone: String,
label: String,
value: String,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
struct SafetyContent {
heading: String,
intro: String,
steps: Vec<SafetyStep>,
links: Vec<LinkContent>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
struct SafetyStep {
label: String,
detail: String,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
struct LinkContent {
label: String,
href: String,
external: bool,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
struct DownloadContent {
heading: String,
intro: String,
cta_label: String,
cta_href: String,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
struct FooterContent {
summary: String,
links: Vec<LinkContent>,
disclaimer: String,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
struct ChangelogContent {
locale: String,
design_contract: String,
meta: MetaContent,
brand: BrandContent,
ui: UiContent,
languages: Vec<LanguageOption>,
navigation: Vec<NavigationItem>,
hero: ChangelogHero,
os: OsHighlights,
repository_eyebrow: String,
repository_heading: String,
repository_intro: String,
changes_label: String,
repositories: Vec<RepositoryUpdate>,
footer: FooterContent,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
struct ChangelogHero {
eyebrow: String,
heading: String,
intro: String,
status_label: String,
status_value: String,
development_label: String,
development_value: String,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
struct OsHighlights {
eyebrow: String,
heading: String,
intro: String,
items: Vec<Highlight>,
links: Vec<LinkContent>,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
struct Highlight {
title: String,
detail: String,
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
struct RepositoryUpdate {
id: String,
index: String,
kicker: String,
name: String,
status: String,
summary: String,
changes: Vec<String>,
links: Vec<LinkContent>,
}
#[cfg(feature = "server")]
thread_local! {
static CONTENT_DB: rusqlite::Connection = {
let path = std::env::var("BIFROST_CONTENT_DB")
.unwrap_or_else(|_| "data/content.db".to_string());
rusqlite::Connection::open(path).expect("failed to open the BifrOSt content database")
};
}
#[get("/api/content/{locale}")]
async fn get_homepage_content(locale: String) -> Result<HomepageContent> {
let payload = CONTENT_DB.with(|database| {
database.query_row(
"SELECT payload FROM page_content WHERE page = ?1 AND locale = ?2",
rusqlite::params!["home", locale],
|row| row.get::<_, String>(0),
)
})?;
Ok(serde_json::from_str(&payload)?)
}
#[get("/api/changelog/{locale}")]
async fn get_changelog_content(locale: String) -> Result<ChangelogContent> {
let payload = CONTENT_DB.with(|database| {
database.query_row(
"SELECT payload FROM changelog_content WHERE locale = ?1",
rusqlite::params![locale],
|row| row.get::<_, String>(0),
)
})?;
Ok(serde_json::from_str(&payload)?)
}
#[derive(Routable, Clone, PartialEq)]
enum Route {
#[route("/")]
HomePage {},
#[route("/changelog")]
ChangelogPage {},
}
fn main() {
dioxus::launch(App);
}
#[component]
fn App() -> Element {
let locale = use_signal(|| "is".to_string());
use_context_provider(|| locale);
let stored_locale = locale;
use_effect(move || {
let mut locale_from_storage = stored_locale;
spawn(async move {
if let Ok(value) =
document::eval(r#"return localStorage.getItem("bifrost-locale");"#).await
&& let Some(code) = value.as_str()
&& matches!(code, "is" | "en")
&& locale_from_storage() != code
{
locale_from_storage.set(code.to_string());
}
});
document::eval(
r#"
if (!window.__bifrostLanguageObserver) {
const syncLanguage = () => {
const shell = document.querySelector(".site-shell[lang]");
if (shell) document.documentElement.lang = shell.lang;
};
const observer = new MutationObserver(syncLanguage);
observer.observe(document.body, {
attributes: true,
attributeFilter: ["lang"],
childList: true,
subtree: true
});
window.__bifrostLanguageObserver = observer;
syncLanguage();
}
"#,
);
});
rsx! {
document::Stylesheet { href: CSS }
document::Meta { name: "theme-color", content: "#07111f" }
Router::<Route> {}
}
}
fn loading_view() -> Element {
rsx! {
div {
class: "content-loading",
aria_busy: "true",
span {}
span {}
span {}
}
}
}
fn select_locale(mut locale: Signal<String>, requested: String) {
let code = if requested == "en" { "en" } else { "is" };
locale.set(code.to_string());
let script = format!(r#"localStorage.setItem("bifrost-locale", {code:?});"#);
document::eval(&script);
}
#[component]
fn SiteHeader(
brand: BrandContent,
ui: UiContent,
languages: Vec<LanguageOption>,
navigation: Vec<NavigationItem>,
current_locale: String,
locale: Signal<String>,
brand_href: String,
) -> Element {
rsx! {
header {
class: "site-header",
a {
class: "brand",
href: "{brand_href}",
aria_label: "{brand.home_aria}",
img { src: "{brand.mark_path}", alt: "", width: "40", height: "40" }
span { "{brand.name}" }
}
div {
class: "header-actions",
nav {
class: "desktop-nav",
aria_label: "{ui.primary_navigation_aria}",
for item in navigation.iter() {
a {
key: "{item.href}",
class: if item.kind == "source" { "nav-source" } else { "" },
href: "{item.href}",
target: if item.external { "_blank" } else { "_self" },
rel: if item.external { "noreferrer" } else { "" },
"{item.label}"
if item.kind == "source" {
span { class: "link-arrow", aria_hidden: "true" }
}
}
}
}
div {
class: "language-switch",
role: "group",
aria_label: "{ui.language_control}",
for option in languages.iter() {
{
let code = option.code.clone();
rsx! {
button {
key: "{option.code}",
class: if option.code == current_locale { "is-active" } else { "" },
aria_pressed: option.code == current_locale,
title: "{option.label}",
onclick: move |_| select_locale(locale, code.clone()),
"{option.short_label}"
}
}
}
}
}
details {
class: "mobile-nav",
summary {
aria_label: "{ui.open_navigation}",
span { class: "menu-line" }
span { class: "menu-line" }
}
nav {
aria_label: "{ui.mobile_navigation_aria}",
for item in navigation.iter() {
a {
key: "mobile-{item.href}",
href: "{item.href}",
target: if item.external { "_blank" } else { "_self" },
rel: if item.external { "noreferrer" } else { "" },
"{item.label}"
}
}
}
}
}
}
}
}
#[component]
fn SiteFooter(brand: BrandContent, footer: FooterContent) -> Element {
rsx! {
footer {
div {
class: "footer-brand",
img { src: "{brand.mark_path}", alt: "", width: "34", height: "34" }
span { "{brand.name}" }
}
p { "{footer.summary}" }
div {
class: "footer-links",
for link in footer.links.iter() {
a {
key: "{link.href}",
href: "{link.href}",
target: if link.external { "_blank" } else { "_self" },
rel: if link.external { "noreferrer" } else { "" },
"{link.label}"
}
}
}
p { class: "footer-note", "{footer.disclaimer}" }
}
}
}
#[component]
fn HomePage() -> Element {
let locale = use_context::<Signal<String>>();
rsx! {
SuspenseBoundary {
fallback: |_| loading_view(),
ContentLoader { locale }
}
}
}
#[component]
fn ContentLoader(mut locale: Signal<String>) -> Element {
let content_resource = use_server_future(move || {
let requested_locale = locale();
async move {
get_homepage_content(requested_locale)
.await
.map_err(|error| error.to_string())
}
})?;
let loaded = content_resource.read();
let Some(result) = loaded.as_ref() else {
return rsx! {};
};
match result {
Ok(content) => rsx! { Homepage { content: content.clone(), locale } },
Err(error) => rsx! {
main { class: "content-error", role: "alert", code { "{error}" } }
},
}
}
#[component]
fn Homepage(content: HomepageContent, mut locale: Signal<String>) -> Element {
let mut active_scene = use_signal(|| 0usize);
let scene_index = active_scene().min(content.experience.scenes.len().saturating_sub(1));
let scene = &content.experience.scenes[scene_index];
rsx! {
document::Title { "{content.meta.title}" }
document::Meta { name: "description", content: "{content.meta.description}" }
div {
class: "site-shell",
lang: "{content.locale}",
div {
class: "direction-contract",
hidden: true,
dangerous_inner_html: "{content.design_contract}",
}
SiteHeader {
brand: content.brand.clone(),
ui: content.ui.clone(),
languages: content.languages.clone(),
navigation: content.navigation.clone(),
current_locale: content.locale.clone(),
locale,
brand_href: "#top".to_string(),
}
main {
id: "top",
section {
class: "hero",
div {
class: "hero-copy",
p { class: "hero-origin", "{content.hero.origin}" }
h1 {
span { "{content.hero.headline_line_1}" }
" "
span { class: "title-accent", "{content.hero.headline_line_2}" }
}
p { class: "hero-lede", "{content.hero.lede}" }
div {
class: "hero-actions",
a {
class: "button button-primary",
href: "{content.hero.primary_href}",
target: "_blank",
rel: "noreferrer",
"{content.hero.primary_label}"
span { class: "link-arrow", aria_hidden: "true" }
}
a {
class: "text-link",
href: "{content.hero.secondary_href}",
"{content.hero.secondary_label}"
}
}
}
div {
class: "hero-visual",
div {
class: "hero-image-wrap",
img {
src: "{content.hero.image_path}",
alt: "{content.hero.image_alt}",
fetchpriority: "high",
}
}
div { class: "hero-marker hero-marker-top", aria_hidden: "true", "{content.hero.marker_top}" }
div { class: "hero-marker hero-marker-bottom", aria_hidden: "true", "{content.hero.marker_bottom}" }
}
div {
class: "aurora-path",
aria_hidden: "true",
span { class: "aurora-line aurora-line-one" }
span { class: "aurora-line aurora-line-two" }
span { class: "aurora-line aurora-line-three" }
}
}
section {
class: "release-ribbon",
for fact in &content.release_facts {
div {
key: "{fact.label}",
span { "{fact.label}" }
strong { "{fact.value}" }
}
}
}
section {
class: "experience-section",
id: "experience",
div {
class: "section-intro",
h2 { "{content.experience.heading}" }
p { "{content.experience.intro}" }
}
div {
class: "scene-stage",
div {
class: "scene-image",
img { src: "{scene.image_path}", alt: "{scene.image_alt}" }
div { class: "image-coordinate", aria_hidden: "true", "{content.experience.image_coordinate}" }
}
div {
class: "scene-notes",
div {
class: "scene-tabs",
role: "tablist",
aria_label: "{content.ui.screenshots_aria}",
for (index, item) in content.experience.scenes.iter().enumerate() {
button {
key: "{item.label}",
class: if scene_index == index { "scene-tab is-active" } else { "scene-tab" },
role: "tab",
aria_selected: scene_index == index,
onclick: move |_| active_scene.set(index),
span { class: "tab-index", "{index + 1}" }
"{item.label}"
}
}
}
div {
class: "scene-caption",
aria_live: "polite",
p { class: "scene-count", "{scene_index + 1:02} / {content.experience.scenes.len():02}" }
h3 { "{scene.title}" }
p { "{scene.note}" }
}
}
}
}
section {
class: "build-section",
id: "built",
div {
class: "build-heading",
h2 { "{content.build.heading}" }
p { "{content.build.intro}" }
}
div {
class: "stack-diagram",
for layer in &content.build.layers {
div {
key: "{layer.label}",
class: "stack-layer stack-{layer.tone}",
span { "{layer.label}" }
strong { "{layer.value}" }
}
}
}
div {
class: "profile-line",
p { "{content.build.profiles_intro}" }
ul {
for profile in &content.build.profiles {
li { key: "{profile}", "{profile}" }
}
}
}
}
section {
class: "safety-section",
id: "install",
div {
class: "safety-heading",
svg {
class: "safety-icon",
view_box: "0 0 48 48",
role: "presentation",
path { d: "M24 4 44 40H4L24 4Z", fill: "none", stroke: "currentColor", stroke_width: "2" }
path { d: "M24 17v11", stroke: "currentColor", stroke_width: "3", stroke_linecap: "round" }
circle { cx: "24", cy: "34", r: "1.8", fill: "currentColor" }
}
h2 { "{content.safety.heading}" }
}
div {
class: "safety-copy",
p { "{content.safety.intro}" }
ol {
for step in &content.safety.steps {
li {
key: "{step.label}",
span { "{step.label}" }
strong { "{step.detail}" }
}
}
}
div {
class: "safety-links",
for link in &content.safety.links {
a {
key: "{link.href}",
href: "{link.href}",
target: if link.external { "_blank" } else { "_self" },
rel: if link.external { "noreferrer" } else { "" },
"{link.label}"
span { class: "link-arrow", aria_hidden: "true" }
}
}
}
}
}
section {
class: "download-section",
div {
class: "download-mark",
img { src: "{content.brand.mark_path}", alt: "", loading: "lazy" }
}
div {
h2 { "{content.download.heading}" }
p { "{content.download.intro}" }
}
a {
class: "button button-light",
href: "{content.download.cta_href}",
target: "_blank",
rel: "noreferrer",
"{content.download.cta_label}"
span { class: "link-arrow", aria_hidden: "true" }
}
}
}
SiteFooter { brand: content.brand.clone(), footer: content.footer.clone() }
}
}
}
#[component]
fn ChangelogPage() -> Element {
let locale = use_context::<Signal<String>>();
rsx! {
SuspenseBoundary {
fallback: |_| loading_view(),
ChangelogLoader { locale }
}
}
}
#[component]
fn ChangelogLoader(mut locale: Signal<String>) -> Element {
let content_resource = use_server_future(move || {
let requested_locale = locale();
async move {
get_changelog_content(requested_locale)
.await
.map_err(|error| error.to_string())
}
})?;
let loaded = content_resource.read();
let Some(result) = loaded.as_ref() else {
return rsx! {};
};
match result {
Ok(content) => rsx! { Changelog { content: content.clone(), locale } },
Err(error) => rsx! {
main { class: "content-error", role: "alert", code { "{error}" } }
},
}
}
#[component]
fn Changelog(content: ChangelogContent, mut locale: Signal<String>) -> Element {
rsx! {
document::Title { "{content.meta.title}" }
document::Meta { name: "description", content: "{content.meta.description}" }
div {
class: "site-shell",
lang: "{content.locale}",
div {
class: "direction-contract",
hidden: true,
dangerous_inner_html: "{content.design_contract}",
}
SiteHeader {
brand: content.brand.clone(),
ui: content.ui.clone(),
languages: content.languages.clone(),
navigation: content.navigation.clone(),
current_locale: content.locale.clone(),
locale,
brand_href: "/".to_string(),
}
main {
id: "top",
class: "changelog-main",
section {
class: "changelog-hero",
div {
class: "changelog-hero-copy",
p { class: "changelog-eyebrow", "{content.hero.eyebrow}" }
h1 { "{content.hero.heading}" }
p { class: "changelog-intro", "{content.hero.intro}" }
}
div {
class: "changelog-status",
div {
span { "{content.hero.status_label}" }
strong { "{content.hero.status_value}" }
}
div {
span { "{content.hero.development_label}" }
strong { "{content.hero.development_value}" }
}
}
div {
class: "changelog-route",
aria_hidden: "true",
span {}
span {}
span {}
}
div {
class: "changelog-trails",
aria_hidden: "true",
span {}
span {}
span {}
}
}
section {
class: "changelog-os",
id: "bifrost",
div {
class: "changelog-section-heading",
p { class: "changelog-eyebrow", "{content.os.eyebrow}" }
h2 { "{content.os.heading}" }
p { "{content.os.intro}" }
}
ol {
class: "highlight-ledger",
for (index, item) in content.os.items.iter().enumerate() {
{
let number = format!("{:02}", index + 1);
rsx! {
li {
key: "{item.title}",
span { class: "highlight-index", "{number}" }
div {
h3 { "{item.title}" }
p { "{item.detail}" }
}
}
}
}
}
}
div {
class: "changelog-links",
for link in &content.os.links {
a {
key: "{link.href}",
href: "{link.href}",
target: if link.external { "_blank" } else { "_self" },
rel: if link.external { "noreferrer" } else { "" },
"{link.label}"
span { class: "link-arrow", aria_hidden: "true" }
}
}
}
}
section {
class: "repository-section",
div {
class: "repository-intro",
p { class: "changelog-eyebrow", "{content.repository_eyebrow}" }
h2 { "{content.repository_heading}" }
p { "{content.repository_intro}" }
}
div {
class: "repository-timeline",
for repository in &content.repositories {
article {
class: "repository-update",
id: "{repository.id}",
div {
class: "repository-rail",
span { "{repository.index}" }
small { "{repository.kicker}" }
}
div {
class: "repository-copy",
div {
class: "repository-title",
h3 { "{repository.name}" }
span { "{repository.status}" }
}
p { class: "repository-summary", "{repository.summary}" }
h4 { "{content.changes_label}" }
ul {
for change in &repository.changes {
li { key: "{change}", "{change}" }
}
}
div {
class: "repository-links",
for link in &repository.links {
a {
key: "{link.href}",
href: "{link.href}",
target: if link.external { "_blank" } else { "_self" },
rel: if link.external { "noreferrer" } else { "" },
"{link.label}"
span { class: "link-arrow", aria_hidden: "true" }
}
}
}
}
}
}
}
}
}
SiteFooter { brand: content.brand.clone(), footer: content.footer.clone() }
}
}
}