Restructure project as Worldshaper
This commit is contained in:
parent
ab891a315c
commit
b4dbd4ee8e
583 changed files with 279 additions and 189269 deletions
35
src/App.tsx
35
src/App.tsx
|
|
@ -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;
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import {
|
|||
getMapDims,
|
||||
getMapRows,
|
||||
resizeRows,
|
||||
} from "./mapEditorShared";
|
||||
} from "./worldshaperShared";
|
||||
|
||||
export function MapLayoutPanel({
|
||||
record,
|
||||
|
|
@ -116,3 +116,4 @@ export function MapLayoutPanel({
|
|||
}
|
||||
|
||||
|
||||
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -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, "");
|
||||
}
|
||||
|
||||
|
|
@ -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();
|
||||
|
||||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -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();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -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 {
|
|||
}
|
||||
`;
|
||||
}
|
||||
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue