Scenario generator for vore roleplay and story ideas. https://scenario-generator.deliciousreya.net/responses
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

150 lines
8.0 KiB

import {
type RollTable, type RollTableAuthor, type RollTableDetails,
type RollTableDetailsAndResults,
type RollTableResult, type RollTableResultFull, type RollTableResultSet, rollTableToString
} from '../../common/rolltable.js';
import escapeHTML from 'escape-html';
import slug from 'slug';
export function htmlTableIdentifier(table: RollTable): string {
if (table.full) {
return slug(table.identifier);
} else {
return slug(table.header);
}
}
export function buildFooter({ creditsUrl, includesResponses, includesGenerator }: { readonly creditsUrl: string, readonly includesResponses: boolean, readonly includesGenerator: boolean }): string {
return `
<footer>
${includesGenerator ? `<noscript><p>⚠ Certain features - copy, select-all/select-none${ includesResponses ? ', reroll offline' : ''} - are currently disabled because Javascript is disabled.</p></noscript>` : '' }
${includesGenerator && includesResponses ? `<p class="requiresJs">💡 You can save this page to be able to generate scenarios offline!</p>` : ''}
<p>
<a href="${encodeURI(creditsUrl)}" rel="help" target="_blank">Project credits/instructions/source code</a>
</p>
</footer>`;
}
export function buildAuthor({ author }: { readonly author: RollTableAuthor }): string {
if (author.url) {
return `<div class="author" data-id="${escapeHTML(`${author.id}`)}"><span class="authorRelation">${escapeHTML(author.relation)}</span> <span class="authorName"><a href="${encodeURI(author.url)}" rel="external nofollow" target="_blank">${escapeHTML(author.name)}</a></span></div>`;
} else {
return `<div class="author" data-id="${escapeHTML(`${author.id}`)}"><span class="authorRelation">${escapeHTML(author.relation)}</span> <span class="authorName">${escapeHTML(author.name)}</span></div>`;
}
}
export function buildSet({ resultSet }: { readonly resultSet: RollTableResultSet }): string {
return `<div class="resultSet" data-id="${escapeHTML(`${resultSet.id}`)}" data-global="${resultSet.global ? 'true' : 'false'}"><span class="setRelation">in ${resultSet.name ? 'the' : 'a'} ${resultSet.global ? 'global' : 'server-local'} set</span>${resultSet.name ? ` <span class="setName">${escapeHTML(resultSet.name)}</span>` : ''}</div>`;
}
export function buildResultAttribution({ result }: { readonly result: RollTableResultFull<RollTable> }): string {
return `<div class="attribution"><div class="attributionBubble">${result.author ? buildAuthor({ author: result.author }) : ''}${buildSet({ resultSet: result.set })}</div></div>`;
}
export function buildGeneratedElement({ result, selected }: { readonly result: RollTableResult, readonly selected: boolean }): string {
return (
`<li class="generatedElement">
<h2 class="generatedHead tableHeader"><label class="generatedLabel" ${result.table.full === 'results' ? `for="selected-${htmlTableIdentifier(result.table)}"` : ''}><span class="tableEmoji">${escapeHTML(result.table.emoji)}</span> <span>${escapeHTML(result.table.title)}</span></label>${result.table.full === 'results' ? `<input class="generatedSelect" id="selected-${htmlTableIdentifier(result.table)}" name="selected-${htmlTableIdentifier(result.table)}" type="checkbox" ${selected ? 'checked' : ''} />` : ''}</h2>
<div class="resultText generated${result.full ? ' attributed' : ''}">${escapeHTML(result.text)}${result.full ? buildResultAttribution({ result }) : ''}</div>
</li>`)
}
export function buildGeneratorPage(
{ results, baseUrl, clientId, creditsUrl, selected, includesResponses }:
{ readonly results: ReadonlyMap<RollTable, RollTableResult>, readonly baseUrl: string, readonly clientId: string, readonly creditsUrl: string, readonly selected: ReadonlySet<RollTable>, readonly includesResponses: boolean }): string {
return `
<div id="generator" class="page">
<form method="post" target="_self" action="${escapeHTML(baseUrl)}" id="generatorWindow" class="window readable">
<h2 id="generatorHead">Your generated scenario</h2>
<ul id="generatedScenario">${Array.from(results.values()).map(result => buildGeneratedElement({ result, selected: selected.has(result.table) })).join('')}</ul>
<div id="generatorControls">
<div id="copyButtons" class="buttons requiresJs">
<button class="button" id="copyMD">Markdown</button>
<button class="button" id="copyBB">BBCode</button>
<button class="button" id="copyEmojiText">Text + Emoji</button>
<button class="button" id="copyText">Text Only</button>
</div>
<div id="rollButtons" class="buttons">
<input type="submit" class="button" id="reroll" name="submit" value="Reroll Selected">
<button class="button requiresJs" id="selectAll">Select All</button>
<button class="button requiresJs" id="selectNone">Select None</button>
</div>
<div id="scenarioButtons" class="buttons">
<a href="${encodeURI(baseUrl)}" class="button" id="rerollAll" draggable="false">New Scenario</a>
<input type="submit" class="button" id="saveScenario" name="submit" formtarget="_blank" value="Get Scenario Link">
</div>
${clientId !== '' || includesResponses ?
`<div id="generatorLinks" class="buttons">
${clientId !== '' ? `<a href="https://discord.com/api/oauth2/authorize?client_id=${encodeURIComponent(clientId)}&permissions=0&scope=applications.commands" class="button" rel="external nofollow" target="_blank" draggable="false">Add to Discord</a>` : ''}
${includesResponses ? `<a href="#responses" class="button" id="responsesLink" draggable="false">View Possible Responses</a>` : ''}
</div>` : ''}
</div>
</form>
${buildFooter({ includesResponses: includesResponses, includesGenerator: true, creditsUrl })}
</div>`;
}
export function buildResponseTypeButton({table}: {readonly table: RollTableDetails}) {
return `<a href="#responses-${htmlTableIdentifier(table)}" class="button" draggable="false">${escapeHTML(table.emoji)} ${escapeHTML(table.name)}</a>`
}
export function buildResponse({result, active}: {readonly result: RollTableResult, readonly active: boolean}) {
return `<li class="response${active ? ' active' : ''}${result.full ? ' attributed' : ''}" data-id="${result.full ? result.mappingId : ''}">
<button class="resultText">${escapeHTML(result.text)}</button>
${result.full ? buildResultAttribution({result}) : ''}
</li>`
}
export function buildResponseList({table, activeResult}: {readonly table: RollTableDetailsAndResults, readonly activeResult?: RollTableResult}) {
return `<li class="responseType window readable" id="responses-${htmlTableIdentifier(table)}">
<div class="responseTypeHead tableHeader">
<span class="tableEmoji">${escapeHTML(table.emoji)}</span>
<h2>${escapeHTML(table.title)}</h2>
</div>
<ul>
${Array.from(table.results.values()).map(result => buildResponse({result, active: result === activeResult})).join('')}
</ul>
</li>`
}
export function buildResponsesPage(
{ tables, results, creditsUrl, includesGenerator }: {
readonly tables: Iterable<RollTableDetailsAndResults>,
readonly results?: ReadonlyMap<RollTable, RollTableResult>,
readonly creditsUrl: string,
readonly includesGenerator: boolean}): string {
return `
<div id="responses" class="page">
<header id="responsesHeader" class="window head">
<h1 id="responsesHead">Possible Responses</h1>
<nav class="buttons" id="responsesHeaderNav">
${Array.from(tables).map(table => buildResponseTypeButton({table})).join('')}
<a href="#generator" class="button" draggable="false">Return to Generator</a>
</nav>
</header>
<ul class="responseLists">
${Array.from(tables).map(table => buildResponseList({table, activeResult: results?.get(table)})).join('')}
</ul>
${buildFooter({ includesResponses: true, includesGenerator, creditsUrl })}
</div>
</body>
</html>`;
}
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 `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>${title}</title>
<script>${script}</script>
<style>${styles}</style>
<noscript><style>${noscriptStyles}</style></noscript>
</head>
<body>
${bodyContent}
</body>
</html>`;
}