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.
 
 
vore-scenario-generator/src/client/generator-entrypoint.ts

244 lines
7.8 KiB

import {
ExportFormat,
exportScenario,
type GeneratedState,
generatedStateToString, getResultFrom,
RolledValues,
RollSelections,
type RollTable,
RollTableDatabase,
type RollTableResult
} from '../common/rolltable';
import {
buildGeneratedElement, copyBBID, copyEmojiTextID,
copyMDID, copyTextID,
htmlTableIdentifier, rerollAllId, rerollId,
selectAllId,
selectedIdPrefix,
selectNoneId
} from '../common/template';
import { DOMLoaded } from './onload';
import { scrapeGeneratedScenario } from './scraper';
import { showPopup } from './popup';
import { pulseElement } from './pulse';
export class Generator {
readonly generator: HTMLElement;
readonly scenario: HTMLUListElement;
readonly copyButtons: HTMLElement;
readonly rollButtons: HTMLElement;
readonly db: RollTableDatabase | undefined;
private readonly rolled = new RolledValues();
private readonly selected = new RollSelections();
get state(): GeneratedState {
return {
final: false,
rolled: this.rolled,
selected: this.selected,
}
}
getTableWithHtmlId(id: string, prefix?: string): RollTable | undefined {
return Array.from(this.rolled.keys()).find(t => id === ((prefix ?? '') + htmlTableIdentifier(t)));
}
selectAll(): this {
this.selected.clear();
for (const check of this.scenario.querySelectorAll('input[type=checkbox]') as Iterable<HTMLInputElement>) {
check.checked = true;
pulseElement(check);
const table = this.getTableWithHtmlId(check.id, selectedIdPrefix);
if (table) {
this.selected.add(table);
}
}
return this
}
selectNone(): this {
this.selected.clear();
for (const check of this.scenario.querySelectorAll('input[type=checkbox]') as Iterable<HTMLInputElement>) {
check.checked = false;
pulseElement(check);
}
return this
}
loadValuesFromDOM(): this {
this.rolled.clear()
this.selected.clear()
const scenario = scrapeGeneratedScenario(this.scenario)
if (!scenario) {
throw Error("Failed to load generated values from DOM")
}
for (const [scrapedTable, scrapedResult] of scenario.rolled) {
const table = this.db?.getTableMatching(scrapedTable) ?? scrapedTable
const result = getResultFrom(table, scrapedResult)
if (scenario.selected.has(scrapedTable)) {
this.selected.add(table)
}
this.rolled.add(result)
}
return this
}
attachHandlers(): this {
this.generator.addEventListener('click', (e) => this.clickHandler(e));
this.generator.addEventListener('change', (e) => this.changeHandler(e));
return this;
}
async copy(format: ExportFormat): Promise<void> {
const exported = exportScenario(Array.from(this.rolled.values()), format)
return navigator.clipboard.writeText(exported)
}
private clickHandler(e: Event): void {
if (e.target instanceof HTMLButtonElement || e.target instanceof HTMLAnchorElement) {
switch (e.target.id) {
case selectNoneId:
this.selectNone()
break
case selectAllId:
this.selectAll()
break
case copyMDID:
this.copy(ExportFormat.Markdown)
.then(() => showPopup(this.copyButtons, `Copied Markdown to clipboard!`, 'success'))
.catch((e) => {
console.error("Failed while copying Markdown:", e)
showPopup(this.copyButtons, `Failed to copy Markdown to clipboard`, 'error')
})
break
case copyBBID:
this.copy(ExportFormat.BBCode)
.then(() => showPopup(this.copyButtons, `Copied BBCode to clipboard!`, 'success'))
.catch((e) => {
console.error("Failed while copying BBCode:", e)
showPopup(this.copyButtons, `Failed to copy BBCode to clipboard`, 'error')
})
break
case copyEmojiTextID:
this.copy(ExportFormat.TextEmoji)
.then(() => showPopup(this.copyButtons, `Copied text (with emojis) to clipboard!`, 'success'))
.catch((e) => {
console.error("Failed while copying text (with emojis):", e)
showPopup(this.copyButtons, `Failed to copy text (with emojis) to clipboard`, 'error')
})
break
case copyTextID:
this.copy(ExportFormat.TextOnly)
.then(() => showPopup(this.copyButtons, `Copied text to clipboard!`, 'success'))
.catch((e) => {
console.error("Failed while copying text:", e)
showPopup(this.copyButtons, `Failed to copy text to clipboard`, 'error')
})
break
case rerollId:
for (const row of this.scenario.querySelectorAll(".generatedElement")) {
if (row.querySelector("input[type=checkbox]:checked")) {
const text = row.querySelector<HTMLElement>(".resultText")
if (text) {
pulseElement(text)
}
}
}
showPopup(this.rollButtons, `only pretending to reroll`, 'warning')
break
case rerollAllId:
for (const row of this.scenario.querySelectorAll(".generatedElement")) {
const check = row.querySelector<HTMLInputElement>("input[type=checkbox]:checked")
if (check) {
check.checked = false
pulseElement(check)
}
const text = row.querySelector<HTMLElement>(".resultText")
if (text) {
pulseElement(text)
}
}
showPopup(this.rollButtons, `only pretending to reroll all`, 'warning')
break
default:
if (e.target.classList.contains("resultText")) {
for (let target: HTMLElement|null = e.target; target && target !== this.generator; target = target.parentElement) {
if (target.classList.contains("generatedElement")) {
const check = target.querySelector<HTMLInputElement>(".generatedSelect")
if (check) {
check.click()
}
}
}
} else {
return
}
}
e.preventDefault()
}
}
private changeHandler(e: Event): void {
if (e.target instanceof HTMLInputElement && e.target.type === 'checkbox' && e.target.id.startsWith(selectedIdPrefix)) {
const check = e.target
const table = this.getTableWithHtmlId(check.id, selectedIdPrefix);
if (table) {
if (check.checked) {
this.selected.add(table);
} else {
this.selected.delete(table);
}
pulseElement(check)
}
}
}
private animationendHandler(e: AnimationEvent): void {
if (e.animationName === "pulse" && e.target instanceof HTMLElement && e.target.classList.contains("pulse")) {
e.target.classList.remove("pulse")
}
}
constructor(generator: HTMLElement, generatorForm: HTMLUListElement, copyButtons: HTMLElement, rollButtons: HTMLElement, db?: RollTableDatabase) {
this.generator = generator;
this.scenario = generatorForm;
this.copyButtons = copyButtons;
this.rollButtons = rollButtons;
this.db = db;
}
}
function initGenerator(db?: RollTableDatabase): Generator {
const generatorFound = document.getElementById('generator');
if (!generatorFound) {
throw Error('generator was not found');
}
const generatedScenarioFound = document.getElementById('generatedScenario');
if (!generatedScenarioFound || !(generatedScenarioFound instanceof HTMLUListElement)) {
throw Error('generated scenario was not found');
}
const copyButtons = document.getElementById("copyButtons")
if (!copyButtons) {
throw Error('copy buttons were not found')
}
const rollButtons = document.getElementById("rollButtons")
if (!rollButtons) {
throw Error('copy buttons were not found')
}
return new Generator(generatorFound, generatedScenarioFound, copyButtons, rollButtons, db).loadValuesFromDOM().attachHandlers();
}
let pendingGenerator: Promise<Generator>|undefined = undefined
export async function prepareGenerator(db?: Promise<RollTableDatabase>): Promise<Generator> {
if (pendingGenerator) {
throw Error(`prepareGenerator should only be called once`)
}
pendingGenerator = DOMLoaded.then(() => db)
.then((promisedDb) => initGenerator(promisedDb))
return pendingGenerator
}
DOMLoaded.then(() => pendingGenerator ?? prepareGenerator())
.then(g => console.info(`loaded generator: ${generatedStateToString(g.state)}`))
.catch(e => console.error('failed to load generator', e))