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.
244 lines
7.8 KiB
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))
|
|
|