- Restructure flat static prototype into pnpm workspace monorepo - apps/game: playable shell with R3F 3D scene, HUD, SpacetimeDB connection - apps/docs: design docs and prototypes - apps/site: landing page - packages/ui: shared Button and Panel primitives - services/spacetimedb: backend module (9 tables, 11 reducers) - Archive legacy static files to archive/legacy-static/ - Game loop: connect, undock, target, approach, dock, mine, sell - Add pnpm-workspace.yaml, tsconfig.base.json, spacetime.json
173 lines
5.7 KiB
JavaScript
173 lines
5.7 KiB
JavaScript
/**
|
|
* On-demand page loader for Babel/JSX scripts.
|
|
*
|
|
* Instead of loading ALL pages as <script type="text/babel"> upfront
|
|
* (which forces Babel to parse ~580KB of JSX at once), this loader
|
|
* fetches individual page scripts only when the user navigates to them,
|
|
* then transforms with Babel and evaluates the result.
|
|
*
|
|
* Must be loaded as a plain <script> BEFORE any page scripts.
|
|
*/
|
|
(function() {
|
|
'use strict';
|
|
|
|
window.GDD = window.GDD || {};
|
|
|
|
var _cache = {}; // pageId -> true (loaded)
|
|
var _loading = {}; // pageId -> Promise
|
|
var _scriptCache = {}; // src -> transformed code
|
|
|
|
var PAGE_SCRIPTS = {
|
|
'overview': 'js/pages/overview.js',
|
|
'architecture': 'js/pages/architecture.js',
|
|
'techstack': 'js/pages/techstack.js',
|
|
'backend': 'js/pages/backend.js',
|
|
'agents': 'js/pages/agents.js',
|
|
'gameplay': 'js/pages/gameplay.js',
|
|
'ships': 'js/pages/ships.js',
|
|
'economy': 'js/pages/economy.js',
|
|
'social': 'js/pages/social.js',
|
|
'ship-ai': 'js/pages/ship-ai.js',
|
|
'roadmap': 'js/pages/roadmap.js',
|
|
'risks': 'js/pages/risks.js',
|
|
'demo-gallery': 'js/pages/demo-gallery.js',
|
|
'demo-starmap': 'js/demos/starmap.js',
|
|
'demo-movement': 'js/demos/movement.js',
|
|
'demo-combat': 'js/demos/combat.js',
|
|
'demo-market': 'js/demos/market.js',
|
|
'demo-fitting': 'js/demos/fitting.js',
|
|
'demo-refining': 'js/demos/refining.js',
|
|
'demo-progression':'js/demos/progression.js',
|
|
'demo-bounty': 'js/demos/bounty.js',
|
|
'demo-gamehud': 'js/demos/gamehud.js',
|
|
'demo-chat': 'js/demos/chat.js',
|
|
'demo-zora': 'js/demos/zora.js',
|
|
'demo-galaxy': 'js/demos/galaxy.js',
|
|
};
|
|
|
|
// Component name map — mirrors the one in app.js
|
|
var PAGE_COMP_NAMES = {
|
|
'overview': 'OverviewPage',
|
|
'architecture': 'ArchitecturePage',
|
|
'techstack': 'TechStackPage',
|
|
'backend': 'BackendPage',
|
|
'agents': 'AgentsPage',
|
|
'gameplay': 'GameplayPage',
|
|
'ships': 'ShipsPage',
|
|
'economy': 'EconomyPage',
|
|
'social': 'SocialPage',
|
|
'ship-ai': 'ShipAIPage',
|
|
'roadmap': 'RoadmapPage',
|
|
'risks': 'RisksPage',
|
|
'demo-gallery': 'DemoGalleryPage',
|
|
'demo-starmap': 'StarMapDemo',
|
|
'demo-movement': 'ShipMovementDemo',
|
|
'demo-combat': 'CombatDemo',
|
|
'demo-market': 'MarketDemo',
|
|
'demo-fitting': 'FittingDemo',
|
|
'demo-refining': 'RefiningDemo',
|
|
'demo-progression':'ProgressionDemo',
|
|
'demo-bounty': 'BountyDemo',
|
|
'demo-gamehud': 'GameHudDemo',
|
|
'demo-chat': 'ChatDemo',
|
|
'demo-zora': 'ZoraDemo',
|
|
'demo-galaxy': 'GalaxyDemo',
|
|
};
|
|
|
|
var _isFileProtocol = (window.location.protocol === 'file:');
|
|
|
|
function fetchAndTransform(src) {
|
|
// Check memory cache first
|
|
if (_scriptCache[src]) {
|
|
return Promise.resolve(_scriptCache[src]);
|
|
}
|
|
|
|
// On file:// protocol, fetch() is blocked by the browser.
|
|
// Try injecting a <script type="text/babel" src> tag instead —
|
|
// Babel standalone's MutationObserver will pick it up.
|
|
if (_isFileProtocol) {
|
|
return new Promise(function(resolve, reject) {
|
|
// Check if component appeared (Babel may have processed it
|
|
// from a previously injected tag)
|
|
var compName = PAGE_COMP_NAMES[Object.keys(PAGE_SCRIPTS).filter(
|
|
function(k) { return PAGE_SCRIPTS[k] === src; }
|
|
)[0]];
|
|
if (compName && window.GDD[compName]) {
|
|
resolve('');
|
|
return;
|
|
}
|
|
reject(new Error(
|
|
'Cannot load page from file:// protocol. ' +
|
|
'Serve this folder via HTTP for full navigation. ' +
|
|
'Run: python3 -m http.server 8000 (then open http://localhost:8000)'
|
|
));
|
|
});
|
|
}
|
|
|
|
return fetch(src)
|
|
.then(function(r) {
|
|
if (!r.ok) throw new Error('HTTP ' + r.status + ' loading ' + src);
|
|
return r.text();
|
|
})
|
|
.then(function(code) {
|
|
// Transform JSX → JS using Babel standalone
|
|
var transformed = Babel.transform(code, {
|
|
presets: ['react'],
|
|
plugins: [],
|
|
}).code;
|
|
|
|
_scriptCache[src] = transformed;
|
|
return transformed;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Load a page script on demand. Returns a Promise.
|
|
*/
|
|
window.GDD.loadPage = function loadPage(pageId) {
|
|
if (_cache[pageId]) return Promise.resolve();
|
|
if (_loading[pageId]) return _loading[pageId];
|
|
|
|
// Check if component was already registered by an inline <script type="text/babel">
|
|
var compName = PAGE_COMP_NAMES[pageId];
|
|
if (compName && window.GDD[compName]) {
|
|
_cache[pageId] = true;
|
|
return Promise.resolve();
|
|
}
|
|
|
|
var src = PAGE_SCRIPTS[pageId];
|
|
if (!src) return Promise.resolve();
|
|
|
|
var promise = fetchAndTransform(src)
|
|
.then(function(code) {
|
|
// Execute in global scope so window.GDD assignments work
|
|
(0, eval)(code);
|
|
_cache[pageId] = true;
|
|
delete _loading[pageId];
|
|
})
|
|
.catch(function(err) {
|
|
delete _loading[pageId];
|
|
console.error('[loader] Failed to load page:', pageId, err);
|
|
throw err;
|
|
});
|
|
|
|
_loading[pageId] = promise;
|
|
return promise;
|
|
};
|
|
|
|
/**
|
|
* Preload a page in the background (no-op if already loaded/loading).
|
|
*/
|
|
window.GDD.preloadPage = function preloadPage(pageId) {
|
|
if (_cache[pageId] || _loading[pageId]) return;
|
|
setTimeout(function() { window.GDD.loadPage(pageId); }, 200);
|
|
};
|
|
|
|
/**
|
|
* Check if a page is loaded and its component is available.
|
|
*/
|
|
window.GDD.isPageLoaded = function isPageLoaded(pageId) {
|
|
return !!_cache[pageId];
|
|
};
|
|
})();
|