import { type IRequestStrict, Router } from 'itty-router'; import type { Database } from '../db/database'; import { CSS, JS } from './bundles/client.generated'; import type { HashedBundled } from '../../common/bundle'; import { getSourceMapFileName, SourceMapExtension, SourceMaps } from './bundles/sourcemaps'; import { collapseWhiteSpace } from 'collapse-white-space'; import { getQuerySingleton, takeLast } from '../request/query'; import { StringTemplateBuilder } from './template'; interface WebEnv { readonly BASE_URL: string, readonly CREDITS_URL: string, readonly DISCORD_APP_ID: string } export function wrapPage( { title, bodyContent, script, styles, noscriptStyles }: { readonly title: string, readonly bodyContent: string, readonly script: string, readonly styles: string, readonly noscriptStyles: string }): string { return ` ${title} ${bodyContent} `; } export function webRouter(base: string) { function getSourceMappedJS(name: keyof typeof JS) { const { bundled, hash }: HashedBundled = JS[name]; return bundled + `\n//# sourceMappingURL=${getSourceMapFileName(name, hash, SourceMapExtension.JS)}`; } function getSourceMappedCSS(name: keyof typeof CSS) { const { bundled, hash }: HashedBundled = CSS[name]; return bundled + `\n/*# sourceMappingURL=${getSourceMapFileName(name, hash, SourceMapExtension.CSS)} */`; } async function handleMainPage(req: IRequestStrict, env: WebEnv, db: Database): Promise { const results = await db.getGeneratorPageForDiscordSet( getQuerySingleton(req.query['server'], takeLast) ?? null); // TODO: use SSR with the Main components here // TODO: handle POSTs by rerolling and redisplaying appropriately - redirect to a GET with text IDs listed // TODO: support json output here const generator = buildGeneratorPage({ creditsUrl: env.CREDITS_URL, clientId: env.DISCORD_APP_ID, generatorTargetUrl: env.BASE_URL, results: results.rolled, editable: !results.final, selected: results.selected, includesResponses: true, builder: StringTemplateBuilder, }) const responses = buildResponsesPage({ tables: Array.from(results.db.tables.values()), results: results.rolled, creditsUrl: env.CREDITS_URL, includesGenerator: true, builder: StringTemplateBuilder, }) const wrapped = wrapPage({ title: 'Vore Scenario Generator', script: getSourceMappedJS('combinedGeneratorResponses'), styles: getSourceMappedCSS('combinedGeneratorResponses'), noscriptStyles: getSourceMappedCSS('noscript'), bodyContent: [generator, responses].join('') }) return collapseWhiteSpace(wrapped, { style: 'html' }); } const router = Router({ base }) .get('/responses', async (req, _env, _db, _ctx) => { // TODO: make this actually just the responses const url = new URL(req.url); url.pathname = base; url.hash = '#responses'; return Response.redirect(url.toString(), 303); }) .get('/generator', async (req, _env, _db, _ctx) => { // TODO: make this actually just the generator const url = new URL(req.url); url.pathname = base; url.hash = '#generator'; return Response.redirect(url.toString(), 303); }) .get('/scenario', async (_req, _env, _db, _ctx) => { // TODO: implement me return new Response('Not yet supported', { status: 404 }); }) .get('/', handleMainPage) .post('/', handleMainPage); for (const key in CSS) { if (CSS.hasOwnProperty(key)) { const result = CSS[key as keyof typeof CSS] if (result.sourceMap) { router.get(`/${getSourceMapFileName(key, result.hash, SourceMapExtension.CSS)}`, () => result.sourceMap) } } } for (const key in JS) { if (JS.hasOwnProperty(key)) { const result = JS[key as keyof typeof JS] if (result.sourceMap) { router.get(`/${getSourceMapFileName(key, result.hash, SourceMapExtension.JS)}`, () => result.sourceMap) } } } for (const [filename, contents] of SourceMaps) { router.get(`/${filename}`, () => contents); } return router; }