This commit is contained in:
parent
fb2f6079cc
commit
934c94cb5c
50 changed files with 10464 additions and 758 deletions
|
|
@ -9,43 +9,96 @@ No permission is granted to copy, modify, distribute, or create derivative works
|
|||
export module Catcrafts:Views_impl;
|
||||
import :Views;
|
||||
import Crafter.Graphics;
|
||||
import Catcrafts.Shared;
|
||||
import std;
|
||||
|
||||
using namespace Crafter;
|
||||
|
||||
namespace Catcrafts {
|
||||
namespace {
|
||||
// Owning handle for the page-chrome root. `std::optional` lets us
|
||||
// defer construction until InitializePage() runs (the CreateInBody
|
||||
// call would otherwise fire at static-init time, before main()).
|
||||
// Owning handle for the page chrome, ONLY when this module created it.
|
||||
// std::optional defers construction until InitializePage() runs — a
|
||||
// CreateInBody at static-init time would fire before main().
|
||||
//
|
||||
// Left empty when the server already rendered the chrome: the handle is
|
||||
// owning, and destroying it would remove the server's markup from the
|
||||
// document.
|
||||
std::optional<Dom::HtmlElement> root;
|
||||
|
||||
// True when the document arrived server-rendered.
|
||||
bool adoptedSsr = false;
|
||||
|
||||
Views::SiteContent content;
|
||||
bool contentLoaded = false;
|
||||
|
||||
Window* activeWindow = nullptr;
|
||||
}
|
||||
|
||||
void SetActiveWindow(Window* window) { activeWindow = window; }
|
||||
|
||||
void SetDocumentTitle(std::string_view title) {
|
||||
// Only meaningful on the client-rendered path. On an SSR'd page the
|
||||
// server already set a correct <title>, and overwriting it with the
|
||||
// same string is pointless work.
|
||||
if (activeWindow && !adoptedSsr) activeWindow->SetTitle(title);
|
||||
}
|
||||
|
||||
bool AdoptedSsr() { return adoptedSsr; }
|
||||
|
||||
std::string ReadBundleFile(std::string_view name) {
|
||||
// cfg.files flattens to the bundle root (Crafter.Build copies by
|
||||
// filename, not by path), so "content/posts.json" is readable as
|
||||
// "posts.json" here. runtime.js has already fetched every files.json
|
||||
// entry into the VFS before _start, so this cannot block on the
|
||||
// network and cannot fail for a file that is in the manifest.
|
||||
std::ifstream in(std::string(name), std::ios::binary);
|
||||
if (!in) return {};
|
||||
std::ostringstream buf;
|
||||
buf << in.rdbuf();
|
||||
return buf.str();
|
||||
}
|
||||
|
||||
const Views::SiteContent& SiteData() {
|
||||
if (!contentLoaded) {
|
||||
contentLoaded = true;
|
||||
// Authored content is compiled into the module; only the
|
||||
// pipeline-generated files ride the VFS.
|
||||
content.projects = Content::Projects();
|
||||
content.products = Content::Products();
|
||||
content.legal = Content::LegalPages();
|
||||
content.demos = Content::Demos();
|
||||
content.posts = LoadPosts(ReadBundleFile("posts.json"));
|
||||
content.rates = LoadRates(ReadBundleFile("rates.json"));
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
void InitializePage() {
|
||||
// Two ways a page can arrive, and they need opposite handling.
|
||||
//
|
||||
// 1. Server-rendered (the normal case): #catcrafts-root already exists,
|
||||
// with the chrome and the route's content in it. Building a second
|
||||
// root here would duplicate the header and footer on screen, and
|
||||
// re-rendering <main> would replace correct markup with identical
|
||||
// markup — a wasted round of DOM work and a visible flash for
|
||||
// nothing. So: adopt, and touch nothing.
|
||||
//
|
||||
// 2. The static fallback shell: Caddy serves the wasm bundle's
|
||||
// index.html when the backend is down (see deploy/Caddyfile.example),
|
||||
// and that document has an empty <body>. Here the app IS the whole
|
||||
// site and has to build the chrome and render the route itself.
|
||||
Dom::HtmlElementPtr existing("catcrafts-root");
|
||||
if (existing.ptr != 0) {
|
||||
adoptedSsr = true;
|
||||
return;
|
||||
}
|
||||
|
||||
root.emplace(Dom::HtmlElement::CreateInBody("div", "catcrafts-root"));
|
||||
root->SetInnerHTML(R"(
|
||||
<header>
|
||||
<div class="nav-container">
|
||||
<a href="/" class="logo">🐱 Catcrafts</a>
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a id="blog-nav-button" style="cursor: pointer;" class="active">Blog</a></li>
|
||||
<li><a href="https://forgejo.catcrafts.net/Catcrafts/">Forgejo</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main id="main"></main>
|
||||
|
||||
<footer>
|
||||
<div class="footer-content">
|
||||
<div class="footer-links">
|
||||
Powered by Crafter.Graphics, Running near native with WASM!
|
||||
<a href="https://forgejo.catcrafts.net/Catcrafts/catcrafts.net">View source</a>
|
||||
</div>
|
||||
<p>© 2026 Catcrafts®. All rights reserved. Crafter® and Catcrafts® are registered trademarks with the EUIPO</p>
|
||||
</div>
|
||||
</footer>)");
|
||||
root->SetInnerHTML(std::format(
|
||||
R"(<header id="cc-header">{}</header>)"
|
||||
R"(<main id="main"></main>)"
|
||||
R"(<footer id="cc-footer">{}</footer>)",
|
||||
Views::RenderNav(RouteKind::Home).Str(),
|
||||
Views::RenderFooter().Str()));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue