Restructure project as Worldshaper

This commit is contained in:
Andraxion 2026-06-26 20:30:30 -04:00
parent ab891a315c
commit b4dbd4ee8e
583 changed files with 279 additions and 189269 deletions

View file

@ -4,7 +4,7 @@ import ConfigSection from "./components/ConfigSection";
import EditorToolbar from "./components/EditorToolbar";
import StatusFooter from "./components/StatusFooter";
import TopNavTabs from "./components/TopNavTabs";
import { openStandaloneMapEditorPopup } from "./mapEditorPopup/windowing";
import { openWorldshaperStudioWindow } from "./worldshaperStudio/windowing";
import {
CONFIG_TAB_TO_KEY,
DIALOGUE_NODE_FIELD_ORDER,
@ -76,7 +76,7 @@ type ValidationWorkerResponse = {
issues: string[];
};
const LAST_ACTIVE_TYPE_STORAGE_KEY = "content-editor-v2:lastActiveType";
const LAST_ACTIVE_TYPE_STORAGE_KEY = "worldshaper:lastActiveType";
const DEFAULT_EDITOR_WORLD_ID_FALLBACK = "overworld";
function getContentRecordsForType(contentDataByType: Record<string, JsonObject>, type: string): JsonObject[] {
@ -1564,27 +1564,27 @@ function App() {
}
}
async function resolveDefaultMapEditorWorldId(): Promise<string> {
async function resolveDefaultWorldshaperStudioWorldId(): Promise<string> {
const payload = await fetchJsonOrThrow<{ worldId?: string; world?: JsonObject }>("/api/world-default");
const resolvedWorldId = String(payload.worldId || payload.world?.id || "").trim();
return resolvedWorldId || DEFAULT_EDITOR_WORLD_ID_FALLBACK;
}
async function handleLaunchMapEditor(): Promise<void> {
async function handleLaunchWorldshaperStudio(): Promise<void> {
try {
setError("");
setStatus("Preparing world editor...");
const nextWorldId = await resolveDefaultMapEditorWorldId().catch(() => DEFAULT_EDITOR_WORLD_ID_FALLBACK);
const popup = openStandaloneMapEditorPopup(nextWorldId, window, { worldId: nextWorldId });
setStatus("Preparing Worldshaper Studio...");
const nextWorldId = await resolveDefaultWorldshaperStudioWorldId().catch(() => DEFAULT_EDITOR_WORLD_ID_FALLBACK);
const popup = openWorldshaperStudioWindow(nextWorldId, window, { worldId: nextWorldId });
if (!popup) {
setError("The browser blocked the world editor popup.");
setStatus("World editor unavailable: popup was blocked.");
setError("The browser blocked the Worldshaper Studio window.");
setStatus("Worldshaper Studio unavailable: studio window was blocked.");
return;
}
setStatus(`Opening world editor for ${nextWorldId}...`);
setStatus(`Opening Worldshaper Studio for ${nextWorldId}...`);
} catch (err: unknown) {
setError(String(err));
setStatus("World editor unavailable: failed to prepare world data.");
setStatus("Worldshaper Studio unavailable: failed to prepare world data.");
}
}
@ -1593,16 +1593,16 @@ function App() {
<header className="header-card">
<div className="header-copy">
<p className="eyebrow">New RPG</p>
<h1>Content Editor V2</h1>
<p className="lede">Canonical editor with tabbed pages, structured editing, and raw JSON fallback.</p>
<h1>Worldshaper</h1>
<p className="lede">Worldbuilding studio with tabbed pages, structured editing, and raw JSON fallback.</p>
</div>
<button
type="button"
className="header-map-editor-btn"
onClick={handleLaunchMapEditor}
onClick={handleLaunchWorldshaperStudio}
disabled={isLoading}
>
<span className="header-map-editor-btn-label">World Editor</span>
<span className="header-map-editor-btn-label">Worldshaper Studio</span>
</button>
</header>
@ -1773,10 +1773,6 @@ function App() {
parsedJsonError={parsedJsonError}
recordDraftError={recordDraftError}
/>
<p className="wiki-link-row">
Documentation: <a href="/wiki" target="_blank" rel="noreferrer">Open Wiki</a>
</p>
</section>
</div>
);
@ -1784,3 +1780,4 @@ function App() {
export default App;

View file

@ -4,7 +4,7 @@ import {
getMapDims,
getMapRows,
resizeRows,
} from "./mapEditorShared";
} from "./worldshaperShared";
export function MapLayoutPanel({
record,
@ -116,3 +116,4 @@ export function MapLayoutPanel({
}

View file

@ -99,7 +99,7 @@ h2 {
inset 0 1px 0 rgba(255, 255, 255, 0.12);
overflow: hidden;
isolation: isolate;
animation: headerMapEditorGlow 3.8s ease-in-out infinite;
animation: headerWorldshaperStudioGlow 3.8s ease-in-out infinite;
}
.header-map-editor-btn::before {
@ -109,7 +109,7 @@ h2 {
background:
linear-gradient(120deg, transparent 0%, rgba(255, 255, 255, 0.16) 35%, transparent 62%);
transform: translateX(-140%);
animation: headerMapEditorShine 3.6s ease-in-out infinite;
animation: headerWorldshaperStudioShine 3.6s ease-in-out infinite;
pointer-events: none;
z-index: 0;
}
@ -133,7 +133,7 @@ h2 {
box-shadow: none;
}
@keyframes headerMapEditorGlow {
@keyframes headerWorldshaperStudioGlow {
0%, 100% {
box-shadow:
0 0 0 1px rgba(161, 255, 206, 0.14),
@ -148,7 +148,7 @@ h2 {
}
}
@keyframes headerMapEditorShine {
@keyframes headerWorldshaperStudioShine {
0%, 14% {
transform: translateX(-140%);
}
@ -317,17 +317,6 @@ button.danger:not(:disabled):hover {
gap: 0.25rem;
}
.wiki-link-row {
margin: 0.6rem 0 0;
color: var(--muted);
font-size: 0.92rem;
}
.wiki-link-row a {
color: #8fcaff;
font-weight: 700;
}
.status-text {
margin: 0;
color: var(--muted);
@ -1335,3 +1324,4 @@ button.danger:not(:disabled):hover {
.map-preview-viewport canvas {
display: block;
}

View file

@ -1,15 +1,15 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
// @ts-nocheck
import type { MapEditorPopupBootstrap } from "../mapEditorPopup/bootstrap";
import type { WorldshaperStudioBootstrap } from "../worldshaperStudio/bootstrap";
import {
loadMapEditorPopupBootstrap,
loadStandaloneWorldEditorPopupBootstrap,
} from "../mapEditorPopup/bootstrap";
import { persistMapHeightViewerBounds } from "../mapEditorPopup/windowing";
import { createDebouncedCallback } from "../mapEditorPopup/debounce";
loadWorldshaperStudioBootstrap,
loadStandaloneWorldshaperBootstrap,
} from "../worldshaperStudio/bootstrap";
import { persistWorldshaperHeightViewerBounds } from "../worldshaperStudio/windowing";
import { createDebouncedCallback } from "../worldshaperStudio/debounce";
const VIEWER_STYLE_ID = "map-height-viewer-styles";
const VIEWER_STYLE_ID = "worldshaper-height-viewer-styles";
function ensureStyles(): void {
let styleEl = document.getElementById(VIEWER_STYLE_ID) as HTMLStyleElement | null;
@ -172,11 +172,11 @@ function renderMessage(title: string, message: string): void {
}
function renderLoading(message: string): void {
renderMessage("Loading height viewer", message);
renderMessage("Loading Worldshaper Height Viewer", message);
}
function renderError(message: string): void {
renderMessage("Height viewer unavailable", message);
renderMessage("Worldshaper Height Viewer unavailable", message);
}
function cloneValue<T>(value: T): T {
@ -191,7 +191,7 @@ function buildViewerMarkup(): string {
<div class="viewer-shell">
<div class="viewer-bar">
<div class="viewer-title">
<strong id="viewerTitle">Height Viewer</strong>
<strong id="viewerTitle">Worldshaper Height Viewer</strong>
<span id="viewerMeta">Previewing current world snapshot.</span>
</div>
<div class="viewer-controls">
@ -211,10 +211,10 @@ function buildViewerMarkup(): string {
`;
}
function startViewer(bootstrap: MapEditorPopupBootstrap): void {
function startViewer(bootstrap: WorldshaperStudioBootstrap): void {
document.body.removeAttribute("style");
document.body.innerHTML = buildViewerMarkup();
document.title = "Height Viewer - " + (bootstrap.mapName || bootstrap.mapId || "Untitled");
document.title = "Worldshaper Height Viewer - " + (bootstrap.mapName || bootstrap.mapId || "Untitled");
const titleEl = document.getElementById("viewerTitle");
const metaEl = document.getElementById("viewerMeta");
@ -653,10 +653,10 @@ function startViewer(bootstrap: MapEditorPopupBootstrap): void {
}
}
titleEl.textContent = bootstrap.mapName || bootstrap.mapId || "Height Viewer";
titleEl.textContent = bootstrap.mapName || bootstrap.mapId || "Worldshaper Height Viewer";
metaEl.textContent = bootstrap.mapId + " | " + mapWidth + "x" + mapHeight + " | tile " + tileSize + "px | " + heightLayers.length + " height patch" + (heightLayers.length === 1 ? "" : "es");
const persistBounds = () => {
persistMapHeightViewerBounds(window);
persistWorldshaperHeightViewerBounds(window);
};
const persistBoundsDeferred = createDebouncedCallback(() => {
persistBounds();
@ -684,10 +684,10 @@ async function initHeightViewer(): Promise<void> {
const token = params.get("token")?.trim() || "";
const requestedWorldId = params.get("worldId")?.trim() || params.get("mapId")?.trim() || "";
let bootstrap = loadMapEditorPopupBootstrap(token);
let bootstrap = loadWorldshaperStudioBootstrap(token);
if (!bootstrap) {
try {
bootstrap = await loadStandaloneWorldEditorPopupBootstrap(requestedWorldId, window.location.origin);
bootstrap = await loadStandaloneWorldshaperBootstrap(requestedWorldId, window.location.origin);
} catch (error) {
renderError(String(error || "Failed to load the height viewer."));
return;
@ -703,3 +703,4 @@ async function initHeightViewer(): Promise<void> {
}
void initHeightViewer();

View file

@ -13,17 +13,17 @@ import type {
RoomLayerPayload,
SpriteCatalogEntry,
TileCatalogEntry,
} from "../components/mapEditorShared";
} from "../components/worldshaperShared";
import {
TILE_COLORS,
buildSpriteCatalog,
buildTileCatalogById,
normalizeMapBackgroundColor,
resizeRows,
} from "../components/mapEditorShared";
} from "../components/worldshaperShared";
import { normalizeImagesPayloadSnapshot } from "./graphicsDocumentHelpers";
export type MapEditorPopupBootstrap = {
export type WorldshaperStudioBootstrap = {
mapId: string;
mapName: string;
width: number;
@ -61,12 +61,12 @@ export type MapEditorPopupBootstrap = {
declare global {
interface Window {
__NEW_RPG_MAP_EDITOR_BOOTSTRAPS__?: Record<string, MapEditorPopupBootstrap>;
__WORLDSHAPER_STUDIO_BOOTSTRAPS__?: Record<string, WorldshaperStudioBootstrap>;
}
}
const POPUP_BOOTSTRAP_STORAGE_KEY_PREFIX = "new-rpg-map-editor-bootstrap:";
const STANDALONE_WORLD_BOOTSTRAP_STORAGE_KEY_PREFIX = "new-rpg-map-editor-standalone-world-bootstrap:";
const POPUP_BOOTSTRAP_STORAGE_KEY_PREFIX = "worldshaper:studio-bootstrap:";
const STANDALONE_WORLD_BOOTSTRAP_STORAGE_KEY_PREFIX = "worldshaper:standalone-world-bootstrap:";
const DEFAULT_WORLD_CHUNK_RADIUS = 1;
const DEFAULT_HEIGHT_BLUR_STEP = 0.1;
@ -101,18 +101,18 @@ function hasMeaningfulBootstrapEditorUi(value: unknown): boolean {
return Object.keys(panelLayouts).length > 0;
}
function cloneBootstrap(bootstrap: MapEditorPopupBootstrap): MapEditorPopupBootstrap {
function cloneBootstrap(bootstrap: WorldshaperStudioBootstrap): WorldshaperStudioBootstrap {
if (typeof structuredClone === "function") {
return structuredClone(bootstrap);
}
return JSON.parse(JSON.stringify(bootstrap)) as MapEditorPopupBootstrap;
return JSON.parse(JSON.stringify(bootstrap)) as WorldshaperStudioBootstrap;
}
function getBootstrapRegistry(hostWindow: Window): Record<string, MapEditorPopupBootstrap> {
if (!hostWindow.__NEW_RPG_MAP_EDITOR_BOOTSTRAPS__) {
hostWindow.__NEW_RPG_MAP_EDITOR_BOOTSTRAPS__ = {};
function getBootstrapRegistry(hostWindow: Window): Record<string, WorldshaperStudioBootstrap> {
if (!hostWindow.__WORLDSHAPER_STUDIO_BOOTSTRAPS__) {
hostWindow.__WORLDSHAPER_STUDIO_BOOTSTRAPS__ = {};
}
return hostWindow.__NEW_RPG_MAP_EDITOR_BOOTSTRAPS__;
return hostWindow.__WORLDSHAPER_STUDIO_BOOTSTRAPS__;
}
function getPopupBootstrapStorageKey(token: string): string {
@ -123,13 +123,13 @@ function getStandaloneWorldBootstrapStorageKey(worldId: string): string {
return STANDALONE_WORLD_BOOTSTRAP_STORAGE_KEY_PREFIX + String(worldId || "").trim();
}
function readBootstrapFromOpener(token: string, popupWindow: Window): MapEditorPopupBootstrap | null {
function readBootstrapFromOpener(token: string, popupWindow: Window): WorldshaperStudioBootstrap | null {
try {
const opener = popupWindow.opener;
if (!opener || opener.closed) {
return null;
}
const registry = opener.__NEW_RPG_MAP_EDITOR_BOOTSTRAPS__;
const registry = opener.__WORLDSHAPER_STUDIO_BOOTSTRAPS__;
const bootstrap = registry?.[token];
return bootstrap ? cloneBootstrap(bootstrap) : null;
} catch {
@ -137,7 +137,7 @@ function readBootstrapFromOpener(token: string, popupWindow: Window): MapEditorP
}
}
function cacheBootstrap(token: string, bootstrap: MapEditorPopupBootstrap, popupWindow: Window): void {
function cacheBootstrap(token: string, bootstrap: WorldshaperStudioBootstrap, popupWindow: Window): void {
try {
popupWindow.sessionStorage.setItem(
getPopupBootstrapStorageKey(token),
@ -148,48 +148,48 @@ function cacheBootstrap(token: string, bootstrap: MapEditorPopupBootstrap, popup
}
}
function readCachedBootstrap(token: string, popupWindow: Window): MapEditorPopupBootstrap | null {
function readCachedBootstrap(token: string, popupWindow: Window): WorldshaperStudioBootstrap | null {
try {
const raw = popupWindow.sessionStorage.getItem(getPopupBootstrapStorageKey(token));
if (!raw) {
return null;
}
return JSON.parse(raw) as MapEditorPopupBootstrap;
return JSON.parse(raw) as WorldshaperStudioBootstrap;
} catch {
return null;
}
}
export function createMapEditorPopupToken(): string {
export function createWorldshaperStudioToken(): string {
return "map-editor-" + Date.now().toString(36) + "-" + Math.random().toString(36).slice(2, 10);
}
export function registerMapEditorPopupBootstrap(
export function registerWorldshaperStudioBootstrap(
token: string,
bootstrap: MapEditorPopupBootstrap,
bootstrap: WorldshaperStudioBootstrap,
hostWindow: Window = window,
): void {
getBootstrapRegistry(hostWindow)[token] = cloneBootstrap(bootstrap);
}
export function clearMapEditorPopupBootstrap(token: string, hostWindow: Window = window): void {
export function clearWorldshaperStudioBootstrap(token: string, hostWindow: Window = window): void {
if (!token) {
return;
}
const registry = hostWindow.__NEW_RPG_MAP_EDITOR_BOOTSTRAPS__;
const registry = hostWindow.__WORLDSHAPER_STUDIO_BOOTSTRAPS__;
if (!registry) {
return;
}
delete registry[token];
if (Object.keys(registry).length === 0) {
delete hostWindow.__NEW_RPG_MAP_EDITOR_BOOTSTRAPS__;
delete hostWindow.__WORLDSHAPER_STUDIO_BOOTSTRAPS__;
}
}
export function loadMapEditorPopupBootstrap(
export function loadWorldshaperStudioBootstrap(
token: string,
popupWindow: Window = window,
): MapEditorPopupBootstrap | null {
): WorldshaperStudioBootstrap | null {
if (!token) {
return null;
}
@ -201,8 +201,8 @@ export function loadMapEditorPopupBootstrap(
return readCachedBootstrap(token, popupWindow);
}
export function cacheStandaloneWorldEditorPopupBootstrap(
bootstrap: MapEditorPopupBootstrap,
export function cacheStandaloneWorldshaperBootstrap(
bootstrap: WorldshaperStudioBootstrap,
popupWindow: Window = window,
): boolean {
const worldId = String(bootstrap?.worldId || bootstrap?.mapId || "").trim();
@ -220,10 +220,10 @@ export function cacheStandaloneWorldEditorPopupBootstrap(
}
}
export function readStandaloneWorldEditorPopupBootstrap(
export function readStandaloneWorldshaperBootstrap(
requestedWorldId: string,
popupWindow: Window = window,
): MapEditorPopupBootstrap | null {
): WorldshaperStudioBootstrap | null {
const worldId = String(requestedWorldId || "").trim();
if (!worldId) {
return null;
@ -233,7 +233,7 @@ export function readStandaloneWorldEditorPopupBootstrap(
if (!raw) {
return null;
}
return JSON.parse(raw) as MapEditorPopupBootstrap;
return JSON.parse(raw) as WorldshaperStudioBootstrap;
} catch {
return null;
}
@ -258,7 +258,7 @@ function normalizeContentPayload(type: string, payload: JsonObject): JsonObject
};
}
async function fetchMapEditorContentBundle(apiBase: string): Promise<Record<string, JsonObject>> {
async function fetchWorldshaperStudioContentBundle(apiBase: string): Promise<Record<string, JsonObject>> {
const typesPayload = await fetchJsonOrThrow<{ types?: string[] }>(getContentApiUrl("/api/types", apiBase));
const fallbackTypes = ["npcs", "npc_templates", "dialogues", "factions", "images"];
const requestedTypes = Array.isArray(typesPayload.types) && typesPayload.types.length > 0
@ -478,17 +478,17 @@ function buildNpcOverlaysFromWorldChunks(
});
}
export async function loadStandaloneWorldEditorPopupBootstrap(
export async function loadStandaloneWorldshaperBootstrap(
requestedWorldId: string,
apiBase: string = window.location.origin,
): Promise<MapEditorPopupBootstrap> {
): Promise<WorldshaperStudioBootstrap> {
const worldId = String(requestedWorldId || "").trim();
if (!worldId) {
throw new Error("A world id is required.");
}
const cachedBootstrap = readStandaloneWorldEditorPopupBootstrap(worldId, window);
const cachedBootstrap = readStandaloneWorldshaperBootstrap(worldId, window);
try {
const contentByType = await fetchMapEditorContentBundle(apiBase);
const contentByType = await fetchWorldshaperStudioContentBundle(apiBase);
const worldInfoPayload = await fetchJsonOrThrow<WorldInfoRoutePayload>(getContentApiUrl(`/api/world/${encodeURIComponent(worldId)}`, apiBase));
const world = worldInfoPayload?.world;
if (!world) {
@ -542,7 +542,7 @@ export async function loadStandaloneWorldEditorPopupBootstrap(
const roomLayers = composeWorldRoomLayers(chunks, chunkWidth, chunkHeight, originChunkX, originChunkY, composedWidth, composedHeight);
const heightLayers = composeWorldHeightLayers(chunks, chunkWidth, chunkHeight, originChunkX, originChunkY);
const npcOverlays = buildNpcOverlaysFromWorldChunks(chunks, spriteCatalog, chunkWidth, chunkHeight, originChunkX, originChunkY);
const bootstrap: MapEditorPopupBootstrap = {
const bootstrap: WorldshaperStudioBootstrap = {
mapId: worldId,
mapName: String(world.name || worldId),
width: composedWidth,
@ -582,7 +582,7 @@ export async function loadStandaloneWorldEditorPopupBootstrap(
chunkY: Math.floor(Number(chunk.chunkY) || 0),
})),
};
cacheStandaloneWorldEditorPopupBootstrap(bootstrap, window);
cacheStandaloneWorldshaperBootstrap(bootstrap, window);
return bootstrap;
} catch (error) {
if (cachedBootstrap) {
@ -591,3 +591,4 @@ export async function loadStandaloneWorldEditorPopupBootstrap(
throw error;
}
}

View file

@ -2,7 +2,7 @@ import { clampFloatingWindowRect } from "./floatingWindowUtils";
const CHANGELOG_SPLASH_WINDOW_KEY = "changelogSplash";
const CHANGELOG_SPLASH_VERSION = "2026-06-22-world-editor-release-v6";
const CHANGELOG_SPLASH_STORAGE_KEY = `content-editor-v2:map-editor:changelog-seen:${CHANGELOG_SPLASH_VERSION}`;
const CHANGELOG_SPLASH_STORAGE_KEY = `worldshaper:studio:changelog-seen:${CHANGELOG_SPLASH_VERSION}`;
const DEFAULT_WIDTH = 700;
const DEFAULT_HEIGHT = 560;
const MIN_WIDTH = 520;
@ -468,3 +468,4 @@ export function createChangelogSplashWindowController(scope: ControllerScope) {
version: CHANGELOG_SPLASH_VERSION,
};
}

View file

@ -1,6 +1,6 @@
import { MAP_EDITOR_THEME_PRESETS, buildMapEditorThemeOverrideCss } from "./themePresets";
import { WORLDSHAPER_THEME_PRESETS, buildWorldshaperStudioThemeOverrideCss } from "./themePresets";
export function buildMapEditorPopupStyles(): string {
export function buildWorldshaperStudioStyles(): string {
return ` :root { color-scheme: dark; }
* { box-sizing: border-box; }
html, body {
@ -4227,11 +4227,11 @@ export function buildMapEditorPopupStyles(): string {
z-index: 0;
pointer-events: none;
}
` + buildMapEditorThemeOverrideCss();
` + buildWorldshaperStudioThemeOverrideCss();
}
export function buildMapEditorPopupMarkup(): string {
const themePresetButtons = MAP_EDITOR_THEME_PRESETS.map((preset) => `
export function buildWorldshaperStudioPopupMarkup(): string {
const themePresetButtons = WORLDSHAPER_THEME_PRESETS.map((preset) => `
<button
class="theme-preset-btn"
data-theme-preset="${preset.id}"
@ -4415,7 +4415,7 @@ ${themePresetButtons}
</button>
</div>
<div class="selector-section-body hidden" id="informationHotkeysSectionBody">
<div class="info-help-panel" aria-label="World editor keyboard help">
<div class="info-help-panel" aria-label="Worldshaper Studio keyboard help">
<div class="info-help-title">Editor Controls</div>
<div class="info-help-list">
<div class="shortcut-row">
@ -4682,9 +4682,6 @@ ${themePresetButtons}
<div class="sidebar-footer-linkbar">
<a class="sidebar-footer-link" href="http://www.andraxion.net" target="_blank" rel="noreferrer">Andraxion Studios</a>
</div>
<div class="sidebar-footer-linkbar">
<a class="sidebar-footer-link" href="/wiki" target="_blank" rel="noreferrer">Wiki</a>
</div>
</div>
</div>
</aside>
@ -4715,8 +4712,9 @@ ${themePresetButtons}
`;
}
export function getMapEditorPopupBodyMarkup(): string {
return buildMapEditorPopupMarkup()
export function getWorldshaperStudioBodyMarkup(): string {
return buildWorldshaperStudioPopupMarkup()
.replace(/^<body>/i, "")
.replace(/<\/body>s*$/i, "");
}

View file

@ -1,12 +1,12 @@
import { getMapEditorPopupBodyMarkup, buildMapEditorPopupStyles } from "./dom";
import { getWorldshaperStudioBodyMarkup, buildWorldshaperStudioStyles } from "./dom";
import {
loadMapEditorPopupBootstrap,
loadStandaloneWorldEditorPopupBootstrap,
loadWorldshaperStudioBootstrap,
loadStandaloneWorldshaperBootstrap,
} from "./bootstrap";
import { startMapEditorPopup } from "./runtime";
import { applyMapEditorThemePreset, fetchEditorSettings, getDefaultEditorSettings } from "./themePresets";
import { startWorldshaperStudio } from "./runtime";
import { applyWorldshaperThemePreset, fetchEditorSettings, getDefaultEditorSettings } from "./themePresets";
const POPUP_STYLE_ID = "map-editor-popup-styles";
const POPUP_STYLE_ID = "worldshaper-studio-styles";
function ensurePopupStyles(): void {
let styleEl = document.getElementById(POPUP_STYLE_ID) as HTMLStyleElement | null;
@ -15,11 +15,11 @@ function ensurePopupStyles(): void {
styleEl.id = POPUP_STYLE_ID;
document.head.appendChild(styleEl);
}
styleEl.textContent = buildMapEditorPopupStyles();
styleEl.textContent = buildWorldshaperStudioStyles();
}
function renderError(message: string): void {
document.title = "TES:VIII The Elder";
document.title = "Worldshaper";
document.body.innerHTML = "";
document.body.style.margin = "0";
document.body.style.minHeight = "100vh";
@ -38,7 +38,7 @@ function renderError(message: string): void {
panel.style.boxShadow = "0 12px 36px rgba(3, 8, 18, 0.45)";
const heading = document.createElement("h1");
heading.textContent = "World editor unavailable";
heading.textContent = "Worldshaper Studio unavailable";
heading.style.margin = "0 0 8px";
heading.style.fontSize = "18px";
@ -54,7 +54,7 @@ function renderError(message: string): void {
}
function renderLoading(message: string): void {
document.title = "TES:VIII The Elder";
document.title = "Worldshaper";
document.body.innerHTML = "";
document.body.style.margin = "0";
document.body.style.minHeight = "100vh";
@ -73,7 +73,7 @@ function renderLoading(message: string): void {
panel.style.boxShadow = "0 12px 36px rgba(3, 8, 18, 0.32)";
const heading = document.createElement("h1");
heading.textContent = "Loading world editor";
heading.textContent = "Loading Worldshaper Studio";
heading.style.margin = "0 0 8px";
heading.style.fontSize = "18px";
@ -88,19 +88,19 @@ function renderLoading(message: string): void {
document.body.appendChild(panel);
}
async function initMapEditorPopup(): Promise<void> {
async function initWorldshaperStudioPopup(): Promise<void> {
ensurePopupStyles();
renderLoading("Preparing world data...");
const params = new URLSearchParams(window.location.search);
const token = params.get("token")?.trim() || "";
const requestedWorldId = params.get("worldId")?.trim() || params.get("mapId")?.trim() || "";
let bootstrap = loadMapEditorPopupBootstrap(token);
let bootstrap = loadWorldshaperStudioBootstrap(token);
if (!bootstrap) {
try {
bootstrap = await loadStandaloneWorldEditorPopupBootstrap(requestedWorldId, window.location.origin);
bootstrap = await loadStandaloneWorldshaperBootstrap(requestedWorldId, window.location.origin);
} catch (error) {
renderError(String(error || "Failed to load the world editor."));
renderError(String(error || "Failed to load the Worldshaper Studio."));
return;
}
}
@ -111,11 +111,12 @@ async function initMapEditorPopup(): Promise<void> {
}
const editorSettings = await fetchEditorSettings(bootstrap.apiBase).catch(() => getDefaultEditorSettings());
applyMapEditorThemePreset(editorSettings.mapEditor.themePreset);
applyWorldshaperThemePreset(editorSettings.worldshaperStudio.themePreset);
document.body.removeAttribute("style");
document.body.innerHTML = getMapEditorPopupBodyMarkup();
document.title = "TES:VIII The Elder " + (bootstrap.mapName || bootstrap.mapId || "Untitled");
startMapEditorPopup(bootstrap, editorSettings);
document.body.innerHTML = getWorldshaperStudioBodyMarkup();
document.title = "Worldshaper Studio - " + (bootstrap.mapName || bootstrap.mapId || "Untitled");
startWorldshaperStudio(bootstrap, editorSettings);
}
void initMapEditorPopup();
void initWorldshaperStudioPopup();

View file

@ -7,7 +7,7 @@ import {
mergeImagesPayloadWithSpritesPayload,
mergeImagesPayloadWithTilesPayload,
} from "../editorCore";
import { resizeRows } from "../components/mapEditorShared";
import { resizeRows } from "../components/worldshaperShared";
import { moveItemRelative } from "./reorderableListController";
function cloneValue(value) {
@ -426,3 +426,4 @@ export function createMapDocumentController(config) {
setContentPayload,
};
}

View file

@ -1,6 +1,6 @@
import { createDebouncedCallback } from "./debounce";
const TOOL_WINDOW_LAYOUT_STORAGE_KEY = "content-editor-v2:map-editor:tool-windows:v1";
const TOOL_WINDOW_LAYOUT_STORAGE_KEY = "worldshaper:studio:tool-windows:v1";
type ToolWindowMode = "inline" | "floating";
@ -303,3 +303,4 @@ export function createPopupSessionStore(initialState: Partial<PopupSessionState>
clearPersistedLayout,
};
}

View file

@ -67,7 +67,7 @@ export function createRenderController(scope) {
})
.catch((error) => {
pixiTileStageControllerPromise = null;
console.error("Failed to load the Pixi world renderer for the world editor.", error);
console.error("Failed to load the Pixi world renderer for the Worldshaper Studio.", error);
throw error;
});
return pixiTileStageControllerPromise;
@ -856,3 +856,4 @@ export function createRenderController(scope) {
patchTileSurfaceCell,
};
}

View file

@ -15,21 +15,21 @@ import {
buildTileCatalogById,
DEFAULT_MAP_BACKGROUND_COLOR,
DEFAULT_TILE_COLOR,
} from "../components/mapEditorShared";
import type { MapEditorPopupBootstrap } from "./bootstrap";
} from "../components/worldshaperShared";
import type { WorldshaperStudioBootstrap } from "./bootstrap";
import {
cacheStandaloneWorldEditorPopupBootstrap,
clearMapEditorPopupBootstrap,
createMapEditorPopupToken,
loadStandaloneWorldEditorPopupBootstrap,
registerMapEditorPopupBootstrap,
cacheStandaloneWorldshaperBootstrap,
clearWorldshaperStudioBootstrap,
createWorldshaperStudioToken,
loadStandaloneWorldshaperBootstrap,
registerWorldshaperStudioBootstrap,
} from "./bootstrap";
import { buildChunkKey, worldToChunkCoord, worldToLocalCoord } from "../worldChunking";
import {
getCenteredMapEditorPopupBounds,
MAP_EDITOR_POPUP_BOUNDS_STORAGE_KEY,
openStandaloneMapHeightViewer,
persistMapEditorPopupBounds,
getCenteredWorldshaperStudioBounds,
WORLDSHAPER_STUDIO_BOUNDS_STORAGE_KEY,
openWorldshaperHeightViewerWindow,
persistWorldshaperStudioBounds,
} from "./windowing";
import { createHistoryController } from "./historyController";
import { createHistoryStateStore } from "./historyStateStore";
@ -71,12 +71,12 @@ import {
normalizeImagesPayloadSnapshot,
} from "./graphicsDocumentHelpers";
import {
DEFAULT_MAP_EDITOR_THEME_PRESET,
applyMapEditorThemePreset,
getMapEditorThemeLabel,
DEFAULT_WORLDSHAPER_THEME_PRESET,
applyWorldshaperThemePreset,
getWorldshaperThemeLabel,
getDefaultEditorSettings,
normalizeEditorSettings,
normalizeMapEditorThemePreset,
normalizeWorldshaperThemePreset,
persistEditorSettings,
} from "./themePresets";
import { createAtTooltip } from "./tooltip";
@ -282,7 +282,7 @@ const MAX_WORLD_CHUNK_CACHE_ENTRIES = 256;
const MAX_DYNAMIC_WORLD_CHUNK_RADIUS = 4;
const TILE_SYMBOL_POOL = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!$%&()*+,-/:;<=>?@[]^_{|}~=";
export function startMapEditorPopup(bootstrap: MapEditorPopupBootstrap, initialEditorSettings: unknown = getDefaultEditorSettings()): void {
export function startWorldshaperStudio(bootstrap: WorldshaperStudioBootstrap, initialEditorSettings: unknown = getDefaultEditorSettings()): void {
function normalizeMapBackgroundColor(value, fallback) {
const f = fallback || DEFAULT_MAP_BACKGROUND_COLOR;
const raw = String(value || "").trim();
@ -516,7 +516,7 @@ export function startMapEditorPopup(bootstrap: MapEditorPopupBootstrap, initialE
const defaultNpcTemplate = cloneValue(bootstrap.defaultNpcTemplate) || {};
const apiBase = String(bootstrap.apiBase || "").replace(/\/+$/, "");
function deriveHistoryStorageKey(mapIdValue) {
return "content-editor-v2:map-history:v2:" + String(mapIdValue || "").trim();
return "worldshaper:world-history:v2:" + String(mapIdValue || "").trim();
}
const layerListEl = document.getElementById("layerList");
const paintPaletteEl = document.getElementById("paintPalette");
@ -664,11 +664,11 @@ export function startMapEditorPopup(bootstrap: MapEditorPopupBootstrap, initialE
});
const mapDocument = mapDocumentStore.state;
function cacheStandaloneMapBootstrap() {
return cacheStandaloneWorldEditorPopupBootstrap(buildCurrentBootstrapSnapshot(), window);
return cacheStandaloneWorldshaperBootstrap(buildCurrentBootstrapSnapshot(), window);
}
function syncDocumentTitle() {
const titleName = String(mapDocument.mapName || currentMapId || "Untitled").trim() || "Untitled";
document.title = "TES:VIII The Elder " + titleName;
document.title = "Worldshaper Studio - " + titleName;
}
function clampZoomLevel(value) {
const normalized = Number(value);
@ -1592,7 +1592,7 @@ export function startMapEditorPopup(bootstrap: MapEditorPopupBootstrap, initialE
const editorUiStore = createEditorUiStore(initialEditorUiState);
const historyState = createHistoryStateStore();
let currentHistoryStorageKey = deriveHistoryStorageKey(currentMapId);
const popupBoundsStorageKey = MAP_EDITOR_POPUP_BOUNDS_STORAGE_KEY;
const popupBoundsStorageKey = WORLDSHAPER_STUDIO_BOUNDS_STORAGE_KEY;
function runtimeEscapeHtml(value) {
return String(value || "")
@ -1604,11 +1604,11 @@ export function startMapEditorPopup(bootstrap: MapEditorPopupBootstrap, initialE
}
function getActiveThemePreset() {
return normalizeMapEditorThemePreset(editorSettingsState?.mapEditor?.themePreset || DEFAULT_MAP_EDITOR_THEME_PRESET);
return normalizeWorldshaperThemePreset(editorSettingsState?.worldshaperStudio?.themePreset || DEFAULT_WORLDSHAPER_THEME_PRESET);
}
function getEditorEngineOverrides() {
return normalizeEngineOverrideEntries(editorSettingsState?.mapEditor?.engineOverrides);
return normalizeEngineOverrideEntries(editorSettingsState?.worldshaperStudio?.engineOverrides);
}
function getEffectiveHeightBlurStep() {
@ -1638,8 +1638,8 @@ export function startMapEditorPopup(bootstrap: MapEditorPopupBootstrap, initialE
const requestedEntries = normalizeEngineOverrideEntries(nextEntries);
editorSettingsState = await persistEditorSettings(apiBase, {
...editorSettingsState,
mapEditor: {
...editorSettingsState.mapEditor,
worldshaperStudio: {
...editorSettingsState.worldshaperStudio,
engineOverrides: requestedEntries,
},
});
@ -1656,7 +1656,7 @@ export function startMapEditorPopup(bootstrap: MapEditorPopupBootstrap, initialE
function refreshThemePresetButtons() {
const activePreset = getActiveThemePreset();
themePresetButtons.forEach((button) => {
const presetId = normalizeMapEditorThemePreset(button.getAttribute("data-theme-preset") || "");
const presetId = normalizeWorldshaperThemePreset(button.getAttribute("data-theme-preset") || "");
button.classList.toggle("active", presetId === activePreset);
button.setAttribute("aria-pressed", presetId === activePreset ? "true" : "false");
});
@ -1665,8 +1665,8 @@ export function startMapEditorPopup(bootstrap: MapEditorPopupBootstrap, initialE
async function saveThemePreset(nextPreset) {
editorSettingsState = await persistEditorSettings(apiBase, {
...editorSettingsState,
mapEditor: {
...editorSettingsState.mapEditor,
worldshaperStudio: {
...editorSettingsState.worldshaperStudio,
themePreset: nextPreset,
},
});
@ -1674,18 +1674,18 @@ export function startMapEditorPopup(bootstrap: MapEditorPopupBootstrap, initialE
}
function applyThemePreset(nextPreset, options) {
const normalizedPreset = normalizeMapEditorThemePreset(nextPreset);
const normalizedPreset = normalizeWorldshaperThemePreset(nextPreset);
editorSettingsState = normalizeEditorSettings({
...editorSettingsState,
mapEditor: {
...editorSettingsState.mapEditor,
worldshaperStudio: {
...editorSettingsState.worldshaperStudio,
themePreset: normalizedPreset,
},
});
applyMapEditorThemePreset(normalizedPreset);
applyWorldshaperThemePreset(normalizedPreset);
refreshThemePresetButtons();
if (!(options && options.silent)) {
setStatus("Theme switched to " + getMapEditorThemeLabel(normalizedPreset) + ".", false);
setStatus("Theme switched to " + getWorldshaperThemeLabel(normalizedPreset) + ".", false);
}
if (!(options && options.persist === false)) {
void saveThemePreset(normalizedPreset).catch((error) => {
@ -2856,7 +2856,7 @@ export function startMapEditorPopup(bootstrap: MapEditorPopupBootstrap, initialE
return true;
}
function buildCurrentBootstrapSnapshot(): MapEditorPopupBootstrap {
function buildCurrentBootstrapSnapshot(): WorldshaperStudioBootstrap {
ensureWorldDocumentCurrent();
const baseLayer = cloneLayers(mapDocument.roomLayers).find((layer) => Number(layer.layer) === 0) || null;
const baseRows = Array.isArray(baseLayer?.rows)
@ -2915,7 +2915,7 @@ export function startMapEditorPopup(bootstrap: MapEditorPopupBootstrap, initialE
try {
window.localStorage.removeItem(popupBoundsStorageKey);
} catch (_err) {}
const nextBounds = getCenteredMapEditorPopupBounds(window);
const nextBounds = getCenteredWorldshaperStudioBounds(window);
try {
window.resizeTo(nextBounds.width, nextBounds.height);
window.moveTo(nextBounds.left, nextBounds.top);
@ -4106,15 +4106,15 @@ export function startMapEditorPopup(bootstrap: MapEditorPopupBootstrap, initialE
}
function openHeightViewerWindow() {
const token = createMapEditorPopupToken();
registerMapEditorPopupBootstrap(token, buildCurrentBootstrapSnapshot(), window);
const popup = openStandaloneMapHeightViewer(currentMapId, token, window);
const token = createWorldshaperStudioToken();
registerWorldshaperStudioBootstrap(token, buildCurrentBootstrapSnapshot(), window);
const popup = openWorldshaperHeightViewerWindow(currentMapId, token, window);
if (!popup) {
setStatus("Height viewer unavailable: popup was blocked.", true);
setStatus("Worldshaper Height Viewer unavailable: viewer window was blocked.", true);
return null;
}
window.setTimeout(() => {
clearMapEditorPopupBootstrap(token, window);
clearWorldshaperStudioBootstrap(token, window);
}, 60_000);
setStatus("Opened height viewer for " + currentMapId + ".", false);
return popup;
@ -5450,7 +5450,7 @@ export function startMapEditorPopup(bootstrap: MapEditorPopupBootstrap, initialE
const importController = createImportController(scope);
const interactionController = createInteractionController(scope);
const persistPopupBounds = () => {
persistMapEditorPopupBounds(window);
persistWorldshaperStudioBounds(window);
};
const persistPopupBoundsDeferred = createDebouncedCallback(() => {
persistPopupBounds();
@ -5490,3 +5490,4 @@ export function startMapEditorPopup(bootstrap: MapEditorPopupBootstrap, initialE
persistPopupBounds();
});
}

View file

@ -1,8 +1,8 @@
import { normalizeEngineOverrideEntries } from "./engineOverrides";
export const DEFAULT_MAP_EDITOR_THEME_PRESET = "azure";
export const DEFAULT_WORLDSHAPER_THEME_PRESET = "azure";
export const MAP_EDITOR_THEME_PRESETS = [
export const WORLDSHAPER_THEME_PRESETS = [
{
id: "azure",
label: "Azure",
@ -173,24 +173,24 @@ export const MAP_EDITOR_THEME_PRESETS = [
},
];
const themePresetIds = new Set(MAP_EDITOR_THEME_PRESETS.map((preset) => preset.id));
const themePresetIds = new Set(WORLDSHAPER_THEME_PRESETS.map((preset) => preset.id));
export function normalizeMapEditorThemePreset(value: unknown): string {
export function normalizeWorldshaperThemePreset(value: unknown): string {
const normalized = String(value || "").trim().toLowerCase();
return themePresetIds.has(normalized) ? normalized : DEFAULT_MAP_EDITOR_THEME_PRESET;
return themePresetIds.has(normalized) ? normalized : DEFAULT_WORLDSHAPER_THEME_PRESET;
}
export function getMapEditorThemePreset(value: unknown) {
const presetId = normalizeMapEditorThemePreset(value);
return MAP_EDITOR_THEME_PRESETS.find((preset) => preset.id === presetId) || MAP_EDITOR_THEME_PRESETS[0];
export function getWorldshaperStudioThemePreset(value: unknown) {
const presetId = normalizeWorldshaperThemePreset(value);
return WORLDSHAPER_THEME_PRESETS.find((preset) => preset.id === presetId) || WORLDSHAPER_THEME_PRESETS[0];
}
export function getMapEditorThemeLabel(value: unknown): string {
return getMapEditorThemePreset(value).label;
export function getWorldshaperThemeLabel(value: unknown): string {
return getWorldshaperStudioThemePreset(value).label;
}
export function applyMapEditorThemePreset(value: unknown, targetDocument: Document = document): string {
const presetId = normalizeMapEditorThemePreset(value);
export function applyWorldshaperThemePreset(value: unknown, targetDocument: Document = document): string {
const presetId = normalizeWorldshaperThemePreset(value);
targetDocument.documentElement.setAttribute("data-editor-theme", presetId);
return presetId;
}
@ -198,8 +198,8 @@ export function applyMapEditorThemePreset(value: unknown, targetDocument: Docume
export function getDefaultEditorSettings() {
return {
schemaVersion: 1,
mapEditor: {
themePreset: DEFAULT_MAP_EDITOR_THEME_PRESET,
worldshaperStudio: {
themePreset: DEFAULT_WORLDSHAPER_THEME_PRESET,
engineOverrides: [],
},
};
@ -210,14 +210,14 @@ export function normalizeEditorSettings(value: unknown) {
const source = value && typeof value === "object" && !Array.isArray(value)
? value as Record<string, unknown>
: {};
const mapEditorSource = source.mapEditor && typeof source.mapEditor === "object" && !Array.isArray(source.mapEditor)
? source.mapEditor as Record<string, unknown>
const worldshaperStudioSource = source.worldshaperStudio && typeof source.worldshaperStudio === "object" && !Array.isArray(source.worldshaperStudio)
? source.worldshaperStudio as Record<string, unknown>
: {};
return {
schemaVersion: typeof source.schemaVersion === "number" ? source.schemaVersion : fallback.schemaVersion,
mapEditor: {
themePreset: normalizeMapEditorThemePreset(mapEditorSource.themePreset),
engineOverrides: normalizeEngineOverrideEntries(mapEditorSource.engineOverrides),
worldshaperStudio: {
themePreset: normalizeWorldshaperThemePreset(worldshaperStudioSource.themePreset),
engineOverrides: normalizeEngineOverrideEntries(worldshaperStudioSource.engineOverrides),
},
};
}
@ -265,9 +265,9 @@ function varsToCss(vars: Record<string, string>): string {
.join(" ");
}
export function buildMapEditorThemeOverrideCss(): string {
const rootCss = `:root { ${varsToCss(MAP_EDITOR_THEME_PRESETS[0].vars)} }`;
const presetCss = MAP_EDITOR_THEME_PRESETS
export function buildWorldshaperStudioThemeOverrideCss(): string {
const rootCss = `:root { ${varsToCss(WORLDSHAPER_THEME_PRESETS[0].vars)} }`;
const presetCss = WORLDSHAPER_THEME_PRESETS
.map((preset) => `html[data-editor-theme="${preset.id}"] { ${varsToCss(preset.vars)} }`)
.join("\n");
@ -544,3 +544,4 @@ export function buildMapEditorThemeOverrideCss(): string {
}
`;
}

View file

@ -5,13 +5,13 @@ export type PopupBounds = {
height: number;
};
export const MAP_EDITOR_POPUP_WINDOW_NAME = "new-rpg-room-editor";
export const MAP_EDITOR_POPUP_BOUNDS_STORAGE_KEY = "content-editor-v2:map-editor-popup-bounds";
export const MAP_HEIGHT_VIEWER_WINDOW_NAME = "new-rpg-map-height-viewer";
export const MAP_HEIGHT_VIEWER_BOUNDS_STORAGE_KEY = "content-editor-v2:map-height-viewer-bounds";
export const WORLDSHAPER_STUDIO_WINDOW_NAME = "worldshaper-studio";
export const WORLDSHAPER_STUDIO_BOUNDS_STORAGE_KEY = "worldshaper:studio-window-bounds";
export const WORLDSHAPER_HEIGHT_VIEWER_WINDOW_NAME = "worldshaper-height-viewer";
export const WORLDSHAPER_HEIGHT_VIEWER_BOUNDS_STORAGE_KEY = "worldshaper:height-viewer-window-bounds";
export function buildStandaloneMapEditorUrl(mapId: string, hostWindow: Window = window, options?: { worldId?: string }): string {
const popupUrl = new URL(`${import.meta.env.BASE_URL}map-editor-popup.html`, hostWindow.location.origin);
export function buildWorldshaperStudioUrl(mapId: string, hostWindow: Window = window, options?: { worldId?: string }): string {
const popupUrl = new URL(`${import.meta.env.BASE_URL}worldshaper-studio.html`, hostWindow.location.origin);
const normalizedMapId = String(mapId || "").trim();
const normalizedWorldId = String(options?.worldId || "").trim();
if (normalizedMapId) {
@ -23,8 +23,8 @@ export function buildStandaloneMapEditorUrl(mapId: string, hostWindow: Window =
return popupUrl.toString();
}
export function buildStandaloneMapHeightViewerUrl(mapId: string, token = "", hostWindow: Window = window): string {
const popupUrl = new URL(`${import.meta.env.BASE_URL}map-height-viewer.html`, hostWindow.location.origin);
export function buildWorldshaperHeightViewerUrl(mapId: string, token = "", hostWindow: Window = window): string {
const popupUrl = new URL(`${import.meta.env.BASE_URL}worldshaper-height-viewer.html`, hostWindow.location.origin);
const normalizedMapId = String(mapId || "").trim();
const normalizedToken = String(token || "").trim();
if (normalizedMapId) {
@ -36,7 +36,7 @@ export function buildStandaloneMapHeightViewerUrl(mapId: string, token = "", hos
return popupUrl.toString();
}
export function getCenteredMapEditorPopupBounds(hostWindow: Window = window): PopupBounds {
export function getCenteredWorldshaperStudioBounds(hostWindow: Window = window): PopupBounds {
const width = 1360;
const height = 900;
const hostScreenX = Number.isFinite(hostWindow.screenX) ? hostWindow.screenX : 0;
@ -52,7 +52,7 @@ export function getCenteredMapEditorPopupBounds(hostWindow: Window = window): Po
return { left, top, width, height };
}
export function getCenteredMapHeightViewerBounds(hostWindow: Window = window): PopupBounds {
export function getCenteredWorldshaperHeightViewerBounds(hostWindow: Window = window): PopupBounds {
const width = 1280;
const height = 820;
const hostScreenX = Number.isFinite(hostWindow.screenX) ? hostWindow.screenX : 0;
@ -68,11 +68,11 @@ export function getCenteredMapHeightViewerBounds(hostWindow: Window = window): P
return { left, top, width, height };
}
export function readMapEditorPopupBounds(hostWindow: Window = window): PopupBounds {
export function readWorldshaperStudioBounds(hostWindow: Window = window): PopupBounds {
try {
const raw = hostWindow.localStorage.getItem(MAP_EDITOR_POPUP_BOUNDS_STORAGE_KEY);
const raw = hostWindow.localStorage.getItem(WORLDSHAPER_STUDIO_BOUNDS_STORAGE_KEY);
if (!raw) {
return getCenteredMapEditorPopupBounds(hostWindow);
return getCenteredWorldshaperStudioBounds(hostWindow);
}
const parsed = JSON.parse(raw) as Partial<PopupBounds>;
const width = Math.max(640, Number(parsed.width) || 0);
@ -80,19 +80,19 @@ export function readMapEditorPopupBounds(hostWindow: Window = window): PopupBoun
const left = Math.max(0, Number(parsed.left) || 0);
const top = Math.max(0, Number(parsed.top) || 0);
if (!Number.isFinite(width) || !Number.isFinite(height) || !Number.isFinite(left) || !Number.isFinite(top)) {
return getCenteredMapEditorPopupBounds(hostWindow);
return getCenteredWorldshaperStudioBounds(hostWindow);
}
return { left, top, width, height };
} catch {
return getCenteredMapEditorPopupBounds(hostWindow);
return getCenteredWorldshaperStudioBounds(hostWindow);
}
}
export function readMapHeightViewerBounds(hostWindow: Window = window): PopupBounds {
export function readWorldshaperHeightViewerBounds(hostWindow: Window = window): PopupBounds {
try {
const raw = hostWindow.localStorage.getItem(MAP_HEIGHT_VIEWER_BOUNDS_STORAGE_KEY);
const raw = hostWindow.localStorage.getItem(WORLDSHAPER_HEIGHT_VIEWER_BOUNDS_STORAGE_KEY);
if (!raw) {
return getCenteredMapHeightViewerBounds(hostWindow);
return getCenteredWorldshaperHeightViewerBounds(hostWindow);
}
const parsed = JSON.parse(raw) as Partial<PopupBounds>;
const width = Math.max(640, Number(parsed.width) || 0);
@ -100,15 +100,15 @@ export function readMapHeightViewerBounds(hostWindow: Window = window): PopupBou
const left = Math.max(0, Number(parsed.left) || 0);
const top = Math.max(0, Number(parsed.top) || 0);
if (!Number.isFinite(width) || !Number.isFinite(height) || !Number.isFinite(left) || !Number.isFinite(top)) {
return getCenteredMapHeightViewerBounds(hostWindow);
return getCenteredWorldshaperHeightViewerBounds(hostWindow);
}
return { left, top, width, height };
} catch {
return getCenteredMapHeightViewerBounds(hostWindow);
return getCenteredWorldshaperHeightViewerBounds(hostWindow);
}
}
export function persistMapEditorPopupBounds(sourceWindow: Window = window): void {
export function persistWorldshaperStudioBounds(sourceWindow: Window = window): void {
if (sourceWindow.closed) {
return;
}
@ -118,7 +118,7 @@ export function persistMapEditorPopupBounds(sourceWindow: Window = window): void
const left = Math.max(0, Math.round(Number(sourceWindow.screenX) || 0));
const top = Math.max(0, Math.round(Number(sourceWindow.screenY) || 0));
sourceWindow.localStorage.setItem(
MAP_EDITOR_POPUP_BOUNDS_STORAGE_KEY,
WORLDSHAPER_STUDIO_BOUNDS_STORAGE_KEY,
JSON.stringify({ left, top, width, height }),
);
} catch {
@ -126,7 +126,7 @@ export function persistMapEditorPopupBounds(sourceWindow: Window = window): void
}
}
export function persistMapHeightViewerBounds(sourceWindow: Window = window): void {
export function persistWorldshaperHeightViewerBounds(sourceWindow: Window = window): void {
if (sourceWindow.closed) {
return;
}
@ -136,7 +136,7 @@ export function persistMapHeightViewerBounds(sourceWindow: Window = window): voi
const left = Math.max(0, Math.round(Number(sourceWindow.screenX) || 0));
const top = Math.max(0, Math.round(Number(sourceWindow.screenY) || 0));
sourceWindow.localStorage.setItem(
MAP_HEIGHT_VIEWER_BOUNDS_STORAGE_KEY,
WORLDSHAPER_HEIGHT_VIEWER_BOUNDS_STORAGE_KEY,
JSON.stringify({ left, top, width, height }),
);
} catch {
@ -144,13 +144,13 @@ export function persistMapHeightViewerBounds(sourceWindow: Window = window): voi
}
}
export function openStandaloneMapEditorPopup(
export function openWorldshaperStudioWindow(
mapId: string,
hostWindow: Window = window,
options?: { worldId?: string },
): Window | null {
const popupUrl = buildStandaloneMapEditorUrl(mapId, hostWindow, options);
const initialBounds = readMapEditorPopupBounds(hostWindow);
const popupUrl = buildWorldshaperStudioUrl(mapId, hostWindow, options);
const initialBounds = readWorldshaperStudioBounds(hostWindow);
const popupFeatures = [
"popup=yes",
"resizable=yes",
@ -161,7 +161,7 @@ export function openStandaloneMapEditorPopup(
"top=" + initialBounds.top,
].join(",");
const popup = hostWindow.open(popupUrl, MAP_EDITOR_POPUP_WINDOW_NAME, popupFeatures);
const popup = hostWindow.open(popupUrl, WORLDSHAPER_STUDIO_WINDOW_NAME, popupFeatures);
if (!popup) {
return null;
}
@ -178,9 +178,9 @@ export function openStandaloneMapEditorPopup(
return popup;
}
export function openStandaloneMapHeightViewer(mapId: string, token = "", hostWindow: Window = window): Window | null {
const popupUrl = buildStandaloneMapHeightViewerUrl(mapId, token, hostWindow);
const initialBounds = readMapHeightViewerBounds(hostWindow);
export function openWorldshaperHeightViewerWindow(mapId: string, token = "", hostWindow: Window = window): Window | null {
const popupUrl = buildWorldshaperHeightViewerUrl(mapId, token, hostWindow);
const initialBounds = readWorldshaperHeightViewerBounds(hostWindow);
const popupFeatures = [
"popup=yes",
"resizable=yes",
@ -191,7 +191,7 @@ export function openStandaloneMapHeightViewer(mapId: string, token = "", hostWin
"top=" + initialBounds.top,
].join(",");
const popup = hostWindow.open(popupUrl, MAP_HEIGHT_VIEWER_WINDOW_NAME, popupFeatures);
const popup = hostWindow.open(popupUrl, WORLDSHAPER_HEIGHT_VIEWER_WINDOW_NAME, popupFeatures);
if (!popup) {
return null;
}
@ -207,3 +207,4 @@ export function openStandaloneMapHeightViewer(mapId: string, token = "", hostWin
popup.focus();
return popup;
}