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.
 
 

172 lines
7.5 KiB

import { FormButton, LinkButton } from './Button';
import { createContext, Fragment } from 'preact';
import { usePopup } from './usePopup';
import { useCallback, useContext } from 'preact/hooks';
import { ExportFormat, exportFormatToString } from '../rolltable';
import {
GeneratedElement,
type GeneratedElementProps,
reconstituteGeneratedElement,
} from './GeneratedElement';
import { IncludesResponses } from './ResponsesPage';
import { tableIdentifier } from './TableHeader';
export const IncludesGenerator = createContext(false)
export interface GeneratorProps {
generatorTargetUrl: string
elements: GeneratedElementProps[]
addToDiscordUrl: string|null
editable: boolean
}
export enum GeneratorSelect {
All = "all",
None = "none",
}
export enum GeneratorReroll {
All = "all",
Selected = "selected",
}
export function reconstituteGenerator(element: HTMLDivElement, partial?: Partial<GeneratorProps>): GeneratorProps {
const addToDiscordUrl = partial?.addToDiscordUrl ?? element.querySelector<HTMLAnchorElement>("#addToDiscord")?.href ?? null
const editable = partial?.editable ?? !!element.querySelector("#rollButtons")
const elements = partial?.elements ??
Array.from(element.querySelector<HTMLUListElement>("#generatedScenario")!.children)
.map(li => reconstituteGeneratedElement(li as HTMLLIElement, editable ? {} : {selected: null}))
const generatorTargetUrl = partial?.generatorTargetUrl ?? element.querySelector<HTMLFormElement>("#generatorWindow")!.action
return {
addToDiscordUrl,
editable,
generatorTargetUrl,
elements: elements,
}
}
export interface GeneratorEvents {
onCopy?: (format: ExportFormat) => Promise<void>
onReroll?: (which: GeneratorReroll) => Promise<void>
onSelect?: (which: GeneratorSelect) => void
onSelectionChange?: (tableId: number, selected: boolean) => void
}
enum GeneratorSelectionState {
All = "All",
Partial = "Some",
None = "None"
}
export function GeneratorPage({ editable, generatorTargetUrl, addToDiscordUrl, onSelectionChange, onSelect, onReroll, onCopy, elements }: GeneratorProps & GeneratorEvents) {
const includesResponses = useContext(IncludesResponses);
const [copyPopupHost, showCopyPopup] = usePopup<HTMLDivElement>()
const [rerollPopupHost, showRerollPopup] = usePopup<HTMLDivElement>()
const copyWrapper = useCallback((format: ExportFormat) => {
if (!onCopy) {
console.error("No copy handler")
return showCopyPopup(`Failed to copy ${exportFormatToString(format)} to clipboard`, 'error')
}
onCopy(format).then(() => {
return showCopyPopup(`Copied ${exportFormatToString(format)} to clipboard`)
}).catch((ex: unknown) => {
console.error(ex)
return showCopyPopup(`Failed to copy ${exportFormatToString(format)} to clipboard`, 'error')
}).catch((ex: unknown) => {
console.error(ex)
})
}, [showCopyPopup, onCopy])
const md = useCallback(() => copyWrapper(ExportFormat.Markdown), [copyWrapper])
const bb = useCallback(() => copyWrapper(ExportFormat.BBCode), [copyWrapper])
const emoji = useCallback(() => copyWrapper(ExportFormat.TextEmoji), [copyWrapper])
const text = useCallback(() => copyWrapper(ExportFormat.TextOnly), [copyWrapper])
const selected = elements.reduce<null|GeneratorSelectionState>((current, next) => {
if (next.selected === null) {
return current
}
switch (current) {
case GeneratorSelectionState.Partial:
return GeneratorSelectionState.Partial
case GeneratorSelectionState.None:
return next.selected ? GeneratorSelectionState.Partial : GeneratorSelectionState.None
case GeneratorSelectionState.All:
return next.selected ? GeneratorSelectionState.All : GeneratorSelectionState.Partial
case null:
return next.selected ? GeneratorSelectionState.All : GeneratorSelectionState.None
}
}, null)
const selectAll = useCallback((ev: Event) => {
if (!onSelect || (ev.currentTarget instanceof HTMLButtonElement && ev.currentTarget.disabled)) {
return
}
onSelect(GeneratorSelect.All)
}, [onSelect, showRerollPopup])
const selectNone = useCallback((ev: Event) => {
if (!onSelect || (ev.currentTarget instanceof HTMLButtonElement && ev.currentTarget.disabled)) {
return
}
onSelect(GeneratorSelect.None)
}, [onSelect, showRerollPopup])
const rerollSelected = useCallback((ev: Event) => {
if (!onReroll || (ev.currentTarget instanceof HTMLButtonElement && ev.currentTarget.disabled)) {
return
}
onReroll(GeneratorReroll.Selected).then(() => {}).catch((ex: unknown) => {
console.error(ex)
return showRerollPopup(`Failed to reroll`, 'error')
}).catch((ex: unknown) => {
console.error(ex)
})
}, [onReroll, showRerollPopup])
const rerollAll = useCallback((ev: Event) => {
if (!onReroll || (ev.currentTarget instanceof HTMLButtonElement && ev.currentTarget.disabled)) {
return
}
onReroll(GeneratorReroll.All).then(() => {}).catch((ex: unknown) => {
console.error(ex)
return showRerollPopup(`Failed to reroll all`, 'error')
}).catch((ex: unknown) => {
console.error(ex)
})
}, [onReroll, showRerollPopup])
return <div id="generator" class="page">
<form method="post" action={generatorTargetUrl} id="generatorWindow" class="window readable">
<h2 id="generatorHead">Your generated scenario</h2>
<ul id="generatedScenario">
{elements.map(i => <GeneratedElement key={tableIdentifier(i.table)} {...i} onSelectionChange={onSelectionChange} />)}
</ul>
<div id="generatorControls">
<div ref={copyPopupHost} id="copyButtons" className="buttons requiresJs jsPopupHost">
<FormButton id="copyMD" type="button" onClick={onCopy && md} disabled={!onCopy}>Markdown</FormButton>
<FormButton id="copyBB" type="button" onClick={onCopy && bb} disabled={!onCopy}>BBCode</FormButton>
<FormButton id="copyEmojiText" type="button" onClick={onCopy && emoji} disabled={!onCopy}>Text + Emoji</FormButton>
<FormButton id="copyText" type="button" onClick={onCopy && text} disabled={!onCopy}>Text Only</FormButton>
</div>
{editable ? <div ref={rerollPopupHost} id="rollButtons" class="buttons jsPopupHost">
<FormButton type="submit" id="reroll" name="submit"
value="reroll" disabled={onReroll && (!selected || selected === GeneratorSelectionState.None)} onClick={onReroll && rerollSelected}>
Reroll {selected === GeneratorSelectionState.All ? 'All' : 'Selected'}
</FormButton>
<FormButton type="button" id="selectAll" class="requiresJs" onClick={selectAll} disabled={!onSelect || selected === GeneratorSelectionState.All}>Select All</FormButton>
<FormButton type="button" id="selectNone" class="requiresJs" onClick={selectNone} disabled={!onSelect || !selected || selected === GeneratorSelectionState.None}>Select None</FormButton>
</div> : null}
<div id="scenarioButtons" class="buttons">
{editable ? <Fragment>
<LinkButton id="rerollAll" href={generatorTargetUrl} external={false} onClick={onReroll && rerollAll}>New Scenario</LinkButton>
<FormButton id="saveScenario" name="submit" value="saveScenario" type="submit">Get Scenario Link</FormButton>
</Fragment> : <Fragment>
<LinkButton id="openInGenerator" href={generatorTargetUrl} external={false}>Open in Generator</LinkButton>
</Fragment>}
</div>
{ addToDiscordUrl || includesResponses ?
<div id="generatorLinks" class="buttons">
{addToDiscordUrl && <LinkButton external={true} id="addToDiscord" href={addToDiscordUrl}>Add to Discord</LinkButton>}
{includesResponses ? <LinkButton external={false} id="goToResponses" href="#responses">View Possible Responses</LinkButton> : null}
</div> : null}
</div>
</form>
</div>
}