feat(ui): add simulation about dialog
Co-Authored-By: GitHub Copilot <noreply@github.com>
This commit is contained in:
parent
7aebac994b
commit
71b3e44e8a
|
|
@ -6,6 +6,7 @@ Notable changes to this project are documented here.
|
|||
|
||||
### Added
|
||||
|
||||
- Add an About dialog explaining the simulation, shards, actors, and persistence.
|
||||
- Docker Compose runs the gateway and each shard in its own container, with one persistent SQLite volume per shard.
|
||||
- Public clients are view-only: the UI hides editing controls and the server rejects remote writes; localhost clients retain editing access.
|
||||
- Expose the Picard actor console over the `/api/actors/ws` WebSocket endpoint.
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ leptos = { version = "0.8", features = ["csr"] }
|
|||
wasm-bindgen = "=0.2.129"
|
||||
wasm-bindgen-futures = "0.4"
|
||||
js-sys = "0.3"
|
||||
web-sys = { version = "0.3", features = ["CanvasRenderingContext2d", "HtmlCanvasElement", "HtmlElement", "MouseEvent", "WheelEvent", "AddEventListenerOptions", "EventTarget", "WebSocket", "MessageEvent", "Location", "Element", "DomRect", "HtmlDivElement", "Window", "Storage"] }
|
||||
web-sys = { version = "0.3", features = ["CanvasRenderingContext2d", "HtmlCanvasElement", "HtmlElement", "HtmlDialogElement", "MouseEvent", "WheelEvent", "AddEventListenerOptions", "EventTarget", "WebSocket", "MessageEvent", "Location", "Element", "DomRect", "HtmlDivElement", "Window", "Storage"] }
|
||||
gloo-net = { version = "0.6", default-features = false, features = ["http", "json"] }
|
||||
gloo-timers = { version = "0.3", features = ["futures"] }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ fn App() -> impl IntoView {
|
|||
let param = RwSignal::new(String::new());
|
||||
let amount = RwSignal::new("5".to_string());
|
||||
let can_edit = RwSignal::new(false);
|
||||
let about_dialog = NodeRef::<leptos::html::Dialog>::new();
|
||||
// (selection counter, people at the time of selection); the counter forces editor rows to rebuild.
|
||||
let editing = RwSignal::new((0u64, Vec::<Person>::new()));
|
||||
|
||||
|
|
@ -293,7 +294,49 @@ fn App() -> impl IntoView {
|
|||
></canvas>
|
||||
</div>
|
||||
<div class="side">
|
||||
<div class="panel-heading">
|
||||
<h3>"World"</h3>
|
||||
<button on:click=move |_| {
|
||||
if let Some(dialog) = about_dialog.get() {
|
||||
let _ = dialog.show_modal();
|
||||
}
|
||||
} aria-label="About World Simulation">"About"</button>
|
||||
</div>
|
||||
<dialog class="about-dialog" node_ref=about_dialog>
|
||||
<div class="about-header">
|
||||
<div>
|
||||
<p class="about-kicker">"WORLD SIMULATION"</p>
|
||||
<h2>"A living world, running in shards"</h2>
|
||||
</div>
|
||||
<button
|
||||
class="about-close"
|
||||
on:click=move |_| {
|
||||
if let Some(dialog) = about_dialog.get() {
|
||||
dialog.close();
|
||||
}
|
||||
}
|
||||
aria-label="Close about dialog"
|
||||
>"Close"</button>
|
||||
</div>
|
||||
<p>
|
||||
"This is an ongoing simulation of a small world. People and wildlife move, gather resources, work, trade, form families, and respond to the rules in the world's Rune behavior script."
|
||||
</p>
|
||||
<h3>"One world, several shards"</h3>
|
||||
<p>
|
||||
"The map is divided into vertical regions called shards. Each shard runs its own part of the simulation, and entities that cross a boundary are handed to the shard that owns their new position. The gateway coordinates their steps and combines their views into this map."
|
||||
</p>
|
||||
<h3>"Actors and persistence"</h3>
|
||||
<p>
|
||||
"Each person, animal, partnership, market, workplace, and cart is managed by an actor. Shards save their own world snapshots, so the simulation can resume after a restart."
|
||||
</p>
|
||||
<div class="about-actions">
|
||||
<button on:click=move |_| {
|
||||
if let Some(dialog) = about_dialog.get() {
|
||||
dialog.close();
|
||||
}
|
||||
} autofocus=true>"Done"</button>
|
||||
</div>
|
||||
</dialog>
|
||||
{move || state.with(|s| s.as_ref().map(|s| {
|
||||
let (h, m) = (s.hour.floor(), (s.hour.fract() * 60.0).floor());
|
||||
view! {
|
||||
|
|
|
|||
67
ui/style.css
67
ui/style.css
|
|
@ -48,6 +48,73 @@ canvas {
|
|||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.panel-heading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.panel-heading h3 {
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.about-dialog {
|
||||
width: min(560px, calc(100vw - 40px));
|
||||
max-height: min(640px, calc(100vh - 40px));
|
||||
padding: 24px;
|
||||
overflow: auto;
|
||||
border: 1px solid #56615c;
|
||||
border-top: 3px solid #9bbf8a;
|
||||
border-radius: 4px;
|
||||
background: #171b19;
|
||||
color: #ddd;
|
||||
font: inherit;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.about-dialog::backdrop {
|
||||
background: rgb(0 0 0 / 72%);
|
||||
}
|
||||
|
||||
.about-dialog h2 {
|
||||
margin: 4px 0 12px;
|
||||
font-size: 20px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.about-dialog h3 {
|
||||
margin: 22px 0 4px;
|
||||
color: #b7d1a8;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.about-dialog p {
|
||||
margin: 8px 0;
|
||||
color: #c2c9c4;
|
||||
}
|
||||
|
||||
.about-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.about-kicker {
|
||||
color: #a7c895 !important;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.about-close {
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.about-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.side input[type=number] {
|
||||
width: 56px;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue