import type { CSSProperties } from "react"; import { useEffect, useState } from "react"; import { openWorldshaperStudioWindow } from "./worldshaperStudio/windowing"; import { CHANGELOG_SECTIONS, CHANGELOG_SPLASH_FOOTNOTE, CHANGELOG_SPLASH_KICKER, CHANGELOG_SPLASH_TITLE, CHANGELOG_SPLASH_VERSION, } from "./worldshaperStudio/changelogData"; import type { ChangelogItem } from "./worldshaperStudio/changelogData"; import launcherBackground from "../background.png"; type WorldDefaultPayload = { worldId?: string; world?: { id?: string; }; }; type LaunchState = "ready" | "opening" | "opened" | "blocked" | "error"; const DEFAULT_EDITOR_WORLD_ID_FALLBACK = "overworld"; async function resolveDefaultWorldId(): Promise { const response = await fetch("/api/world-default"); if (!response.ok) { throw new Error(`Failed to load default world (${response.status}).`); } const payload = await response.json() as WorldDefaultPayload; const resolvedWorldId = String(payload.worldId || payload.world?.id || "").trim(); return resolvedWorldId || DEFAULT_EDITOR_WORLD_ID_FALLBACK; } function openStudioPopup(worldId: string): boolean { const popup = openWorldshaperStudioWindow(worldId, window, { worldId }); return Boolean(popup); } function openRepo(): void { window.location.assign("https://repo.andraxion.net/"); } function WorldshaperLauncher() { const [launchState, setLaunchState] = useState("ready"); const [status, setStatus] = useState("Launch Worldshaper Studio in its floating window."); const [error, setError] = useState(""); const [worldId, setWorldId] = useState(DEFAULT_EDITOR_WORLD_ID_FALLBACK); useEffect(() => { let cancelled = false; void resolveDefaultWorldId() .then((resolvedWorldId) => { if (cancelled) { return; } setWorldId(resolvedWorldId); }) .catch(() => { if (cancelled) { return; } setWorldId(DEFAULT_EDITOR_WORLD_ID_FALLBACK); }); return () => { cancelled = true; }; }, []); async function handleLaunch(): Promise { setError(""); const nextWorldId = worldId || DEFAULT_EDITOR_WORLD_ID_FALLBACK; setLaunchState("opening"); setStatus(`Opening Worldshaper Studio for ${nextWorldId}...`); try { const resolvedWorldId = nextWorldId || await resolveDefaultWorldId().catch(() => DEFAULT_EDITOR_WORLD_ID_FALLBACK); setWorldId(resolvedWorldId); setStatus(`Opening Worldshaper Studio for ${resolvedWorldId}...`); if (openStudioPopup(resolvedWorldId)) { setLaunchState("opened"); setStatus("Worldshaper Studio opened in a separate window."); return; } setLaunchState("blocked"); setStatus("Your browser blocked the studio window. Use the launch button again after allowing popups."); } catch (nextError: unknown) { const nextErrorText = String(nextError || "Failed to prepare Worldshaper Studio."); setLaunchState("error"); setError(nextErrorText); setStatus("Worldshaper Studio unavailable."); } } const isBusy = launchState === "opening"; return (
Worldshaper Studio
Floating editor launch

New RPG

Worldshaper Studio

{status}

{launchState === "blocked" ? (

Allow the popup, then use the studio button again to launch the floating editor window.

) : null} {launchState === "opened" ? (

The studio is open in its own slim window. This page stays behind as your release board and relaunch point.

) : null} {launchState === "ready" ? (

The editor is designed to live in its own floating window, so the launcher keeps the first step clean.

) : null} {error ?

{error}

: null}
What's New
Current release highlights
{CHANGELOG_SPLASH_KICKER}
{CHANGELOG_SPLASH_TITLE}
Release {CHANGELOG_SPLASH_VERSION}
{CHANGELOG_SECTIONS.map((section) => (

{section.title}

    {section.items.map((item, index) => { const key = `${section.title}-${index}`; const normalizedItem: ChangelogItem = item; if (typeof normalizedItem === "string") { return
  • {normalizedItem}
  • ; } return (
  • {normalizedItem.text}
    {normalizedItem.note ?
    {normalizedItem.note}
    : null}
  • ); })}
))}
{CHANGELOG_SPLASH_FOOTNOTE}
); } export default WorldshaperLauncher;