@mine-scripters/minecraft-event-driven-form
Version:
Event driven minecraft forms
407 lines (400 loc) • 14 kB
JavaScript
import { ButtonDialogueResponse, InputScriptDialogueResponse, DialogueRejectedResponse, inputScriptDialogue, inputSlider, inputDropdown, inputText, inputToggle, dualButtonScriptDialogue, multiButtonScriptDialogue } from '@mine-scripters/minecraft-script-dialogue';
class FormError extends Error {
constructor(msg) {
super(msg);
}
}
class FormArgumentError extends FormError {
path;
step;
current;
constructor(path, step, current) {
super(`Invalid path: ${path} at step: ${step} in object: ${JSON.stringify(current)}`);
this.path = path;
this.step = step;
this.current = current;
}
}
class FormArguments {
_args = {};
set(name, arg) {
this._args[name] = arg;
}
setAll(args) {
this._args = {
...this._args,
...args,
};
}
getAll() {
return this._args;
}
get(name) {
return this._args[name];
}
resolvePath(path) {
let current = this._args;
const splitPath = path.split('.');
for (const step of splitPath) {
if (typeof current === 'object' && step in current) {
current = current[step];
}
else {
throw new FormArgumentError(path, step, current);
}
}
return current;
}
resolveTemplate(template) {
return template.replace(/\{\s*([^}\s]+)\s*\}/g, (_, p1) => {
return this.resolvePath(p1).toString();
});
}
normalize(content) {
if (typeof content === 'string') {
return {
type: 'text',
text: this.resolveTemplate(content),
};
}
else if (Array.isArray(content)) {
return {
type: 'array',
array: content.map((c) => this.normalize(c)),
};
}
else {
return {
type: 'translate',
translate: this.resolveTemplate(content.translate),
args: content.args ? content.args.map((a) => this.normalize(a)) : undefined,
};
}
}
}
class FormEventProducer {
_hub;
_formAction;
_args;
static fromFormHub(hub) {
if (typeof hub.entrypoint === 'string') {
return new FormEventProducer(hub, {
form: hub.entrypoint,
});
}
else {
return new FormEventProducer(hub, {
form: hub.entrypoint.form,
event: hub.entrypoint.events,
eventArgs: hub.entrypoint.eventArgs,
setArgs: hub.entrypoint.initialArgs,
});
}
}
constructor(hub, formAction, previousArgs) {
this._hub = hub;
this._formAction = formAction;
this._args = new FormArguments();
if (this._formAction?.copyArgs && previousArgs) {
this._args.setAll(previousArgs.getAll());
}
if (this._formAction?.setArgs) {
this._args.setAll(this._formAction.setArgs);
}
}
get args() {
return this._args;
}
getInitialForm() {
return this._formAction?.form ? this._hub.forms[this._formAction.form] : undefined;
}
*iterator() {
if (this._formAction) {
if (!this._formAction.event) {
yield new FormEvent(this._hub, undefined, this._args);
}
else if (typeof this._formAction.event === 'string') {
yield new FormEvent(this._hub, {
event: this._formAction.event,
args: this._formAction.eventArgs,
}, this._args);
}
else {
for (const event of this._formAction.event) {
yield new FormEvent(this._hub, {
event: event.event,
args: event.args ?? this._formAction.eventArgs,
}, this._args);
}
}
}
}
}
class FormEvent {
_form = undefined;
_name = undefined;
_continueProcessing = true;
_hub;
_args;
_eventArgs = [];
constructor(hub, eventAction, args) {
this._hub = hub;
this._args = args;
if (eventAction) {
this._name = eventAction.event;
if (eventAction.args) {
this._eventArgs = eventAction.args;
}
}
}
loadForm(name, type) {
if (name in this._hub.forms) {
const form = this._hub.forms[name];
if (type && form.type !== type) {
throw new FormError(`Invalid type ${type} for form named ${name}. The actual type is ${form.type}`);
}
return JSON.parse(JSON.stringify(form));
}
throw new FormError(`Unknown form named ${name}`);
}
set form(form) {
this._form = form;
}
get form() {
return this._form;
}
get name() {
return this._name;
}
get args() {
return this._args;
}
get eventArgs() {
return this._eventArgs;
}
get continueProcessing() {
return this._continueProcessing;
}
cancelProcessing() {
this._continueProcessing = false;
}
}
const triggerEvent = async (eventProducer, receiver) => {
let form = eventProducer.getInitialForm();
if (receiver) {
for (const event of eventProducer.iterator()) {
event.form = form;
if (event.name) {
if (typeof receiver === 'function') {
await receiver(event);
}
else if (event.name in receiver) {
await receiver[event.name](event);
}
}
form = event.form;
if (!event.continueProcessing) {
break;
}
}
}
return form;
};
const _ = (value, ...args) => ({
translate: value,
args: args.length > 0 ? args : undefined,
});
const assertNever = (arg) => {
throw new Error(`Should have been never but got ${arg} instead`);
};
const toRawMessage = (content) => {
switch (content.type) {
case 'text':
return {
text: content.text,
};
case 'translate':
if (!content.args) {
return {
translate: content.translate,
};
}
return {
translate: content.translate,
with: {
rawtext: content.args.map((t) => {
if (typeof t === 'string') {
return {
text: t,
};
}
else {
return toRawMessage(t);
}
}),
},
};
case 'array':
return {
rawtext: content.array.map(toRawMessage),
};
}
assertNever(content);
};
const configureFormMultiButton = (form, args) => {
let dialogue;
dialogue = multiButtonScriptDialogue(toRawMessage(args.normalize(form.title)));
if (form.body) {
dialogue = dialogue.setBody(toRawMessage(args.normalize(form.body)));
}
for (let i = 0; i < form.elements.length; ++i) {
const element = form.elements[i];
if (element.type === 'button') {
dialogue = dialogue.addButton(i.toString(), toRawMessage(args.normalize(element.text)), element.icon);
}
else if (element.type === 'divider') {
dialogue = dialogue.addDivider();
}
else if (element.type === 'header') {
dialogue = dialogue.addHeader(toRawMessage(args.normalize(element.text)));
}
else if (element.type === 'label') {
dialogue = dialogue.addLabel(toRawMessage(args.normalize(element.text)));
}
else {
assertNever(element);
}
}
return dialogue;
};
const configureFormDualButton = (form, args) => {
let dialogue;
dialogue = dualButtonScriptDialogue(toRawMessage(args.normalize(form.title)), {
name: 'top',
text: toRawMessage(args.normalize(form.topButton.text)),
}, {
name: 'bottom',
text: toRawMessage(args.normalize(form.bottomButton.text)),
});
if (form.body) {
dialogue = dialogue.setBody(toRawMessage(args.normalize(form.body)));
}
return dialogue;
};
const configureFormInput = (form, args) => {
let dialogue;
dialogue = inputScriptDialogue(toRawMessage(args.normalize(form.title)));
if (form.submit) {
dialogue = dialogue.withSubmitButton(toRawMessage(args.normalize(form.submit)));
}
for (let i = 0; i < form.elements.length; ++i) {
const element = form.elements[i];
if (element.type === 'slider') {
let input = inputSlider(element.name ?? i.toString(), toRawMessage(args.normalize(element.text)), element.min, element.max, element.step, element.defaultValue);
if (element.tooltip) {
input = input.withTooltip(toRawMessage(args.normalize(element.tooltip)));
}
dialogue = dialogue.addElement(input);
}
else if (element.type === 'dropdown') {
let dropdown = inputDropdown(element.name ?? i.toString(), toRawMessage(args.normalize(element.text)));
if (element.defaultValue) {
const defaultIndex = element.options.findIndex((o) => o.value === element.defaultValue);
dropdown = dropdown.setDefaultValueIndex(defaultIndex === -1 ? 0 : defaultIndex);
}
for (let j = 0; j < element.options.length; ++j) {
const option = element.options[j];
dropdown = dropdown.addOption(toRawMessage(args.normalize(option.text)), option.value);
}
if (element.tooltip) {
dropdown = dropdown.withTooltip(toRawMessage(args.normalize(element.tooltip)));
}
dialogue = dialogue.addElement(dropdown);
}
else if (element.type === 'text') {
let input = inputText(element.name ?? i.toString(), toRawMessage(args.normalize(element.text)), toRawMessage(args.normalize(element.placeholder)), element.defaultValue);
if (element.tooltip) {
input = input.withTooltip(toRawMessage(args.normalize(element.tooltip)));
}
dialogue = dialogue.addElement(input);
}
else if (element.type === 'toggle') {
let input = inputToggle(element.name ?? i.toString(), toRawMessage(args.normalize(element.text)), element.defaultValue);
if (element.tooltip) {
input = input.withTooltip(toRawMessage(args.normalize(element.tooltip)));
}
dialogue = dialogue.addElement(input);
}
else if (element.type === 'label') {
dialogue = dialogue.addLabel(toRawMessage(args.normalize(element.text)));
}
else if (element.type === 'divider') {
dialogue = dialogue.addDivider();
}
else if (element.type === 'header') {
dialogue = dialogue.addHeader(toRawMessage(args.normalize(element.text)));
}
else {
assertNever(element);
}
}
return dialogue;
};
const configureForm = (form, args) => {
switch (form.type) {
case 'multi-button':
return configureFormMultiButton(form, args);
case 'dual-button':
return configureFormDualButton(form, args);
case 'input':
return configureFormInput(form, args);
}
assertNever(form);
};
const renderForm = async (player, formHub, form, args) => {
const dialogue = configureForm(form, args);
const response = await dialogue.open({ player });
if (response instanceof ButtonDialogueResponse) {
if (form.type === 'multi-button') {
const multiButtonForm = form;
const selected = parseInt(response.selected);
return new FormEventProducer(formHub, multiButtonForm.elements[selected].action, args);
}
else if (form.type === 'dual-button') {
const dualButtonForm = form;
if (response.selected === 'top') {
return new FormEventProducer(formHub, dualButtonForm.topButton.action, args);
}
else if (response.selected === 'bottom') {
return new FormEventProducer(formHub, dualButtonForm.bottomButton.action, args);
}
}
}
else if (response instanceof InputScriptDialogueResponse) {
const inputForm = form;
const event = new FormEventProducer(formHub, {
...inputForm.action,
setArgs: {
...inputForm.action?.setArgs,
...response.values,
},
}, args);
return event;
}
else if (response instanceof DialogueRejectedResponse) {
console.error('Dialogue rejected: Exception:', response.exception, 'reason:', response.reason);
}
return new FormEventProducer(formHub);
};
const renderLoop = async (player, formHub, receiver) => {
const initialProducer = FormEventProducer.fromFormHub(formHub);
let currentForm = await triggerEvent(initialProducer, receiver);
let args = initialProducer.args;
while (currentForm !== undefined) {
const eventProducer = await renderForm(player, formHub, currentForm, args);
currentForm = await triggerEvent(eventProducer, receiver);
args = eventProducer.args;
}
};
export { FormArgumentError, FormArguments, FormError, FormEvent, FormEventProducer, _, renderLoop, triggerEvent };
//# sourceMappingURL=MinecraftEventDrivenForm.js.map