138 lines
3.6 KiB
TypeScript
138 lines
3.6 KiB
TypeScript
/* eslint-disable react-hooks/rules-of-hooks */
|
|
import { ActionEvent } from '@hotwired/stimulus';
|
|
import { httpRequest, getConfig } from '@utils';
|
|
import { matchInputElement } from '@coldwired/utils';
|
|
|
|
import { AutoUpload } from '../shared/activestorage/auto-upload';
|
|
import { ApplicationController } from './application_controller';
|
|
|
|
const {
|
|
autosave: { debounce_delay }
|
|
} = getConfig();
|
|
|
|
const AUTOSAVE_DEBOUNCE_DELAY = debounce_delay;
|
|
|
|
export class TypeDeChampEditorController extends ApplicationController {
|
|
static values = {
|
|
typeDeChampStableId: String,
|
|
moveUpUrl: String,
|
|
moveDownUrl: String
|
|
};
|
|
|
|
declare readonly moveUpUrlValue: string;
|
|
declare readonly moveDownUrlValue: string;
|
|
declare readonly isVisible: boolean;
|
|
|
|
#latestPromise = Promise.resolve();
|
|
#dirtyForms: Set<HTMLFormElement> = new Set();
|
|
#inFlightForms: Map<HTMLFormElement, AbortController> = new Map();
|
|
|
|
connect() {
|
|
this.#latestPromise = Promise.resolve();
|
|
this.on('change', (event) => this.onChange(event));
|
|
this.on('input', (event) => this.onInput(event));
|
|
}
|
|
|
|
disconnect() {
|
|
this.#latestPromise = Promise.resolve();
|
|
for (const [form] of this.#inFlightForms) {
|
|
this.abortForm(form);
|
|
}
|
|
this.#inFlightForms.clear();
|
|
}
|
|
|
|
onMoveButtonClick(event: ActionEvent) {
|
|
const { direction } = event.params;
|
|
const action =
|
|
direction == 'up' ? this.moveUpUrlValue : this.moveDownUrlValue;
|
|
const form = createForm(action, 'patch');
|
|
this.requestSubmitForm(form);
|
|
}
|
|
|
|
private onChange(event: Event) {
|
|
matchInputElement(event.target, {
|
|
file: (target) => {
|
|
if (target.files?.length) {
|
|
const autoupload = new AutoUpload(target, target.files[0]);
|
|
autoupload.start();
|
|
}
|
|
},
|
|
changeable: (target) => this.save(target.form)
|
|
});
|
|
}
|
|
|
|
private onInput(event: Event) {
|
|
matchInputElement(event.target, {
|
|
inputable: (target) => {
|
|
if (target.form) {
|
|
this.#dirtyForms.add(target.form);
|
|
this.debounce(this.save, AUTOSAVE_DEBOUNCE_DELAY);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
private save(form?: HTMLFormElement | null): void {
|
|
if (form) {
|
|
createHiddenInput(form, 'should_render', true);
|
|
} else {
|
|
this.element.querySelector('input[name="should_render"]')?.remove();
|
|
}
|
|
|
|
this.requestSubmitForm(form);
|
|
}
|
|
|
|
private requestSubmitForm(form?: HTMLFormElement | null) {
|
|
if (form) {
|
|
this.submitForm(form);
|
|
} else {
|
|
const forms = [...this.#dirtyForms];
|
|
this.#dirtyForms.clear();
|
|
|
|
for (const form of forms) {
|
|
this.submitForm(form);
|
|
}
|
|
}
|
|
}
|
|
|
|
private submitForm(form: HTMLFormElement) {
|
|
const controller = this.abortForm(form);
|
|
|
|
this.#latestPromise = this.#latestPromise.finally(() =>
|
|
httpRequest(form.action, {
|
|
method: form.getAttribute('method') ?? '',
|
|
body: new FormData(form),
|
|
controller: controller
|
|
})
|
|
.turbo()
|
|
.catch(() => null)
|
|
);
|
|
}
|
|
|
|
private abortForm(form: HTMLFormElement) {
|
|
const controller = new AbortController();
|
|
this.#inFlightForms.get(form)?.abort();
|
|
this.#inFlightForms.set(form, controller);
|
|
return controller;
|
|
}
|
|
}
|
|
|
|
function createForm(action: string, method: string) {
|
|
const form = document.createElement('form');
|
|
form.action = action;
|
|
form.method = 'post';
|
|
createHiddenInput(form, '_method', method);
|
|
return form;
|
|
}
|
|
|
|
function createHiddenInput(
|
|
form: HTMLFormElement,
|
|
name: string,
|
|
value: unknown
|
|
) {
|
|
const input = document.createElement('input');
|
|
input.type = 'hidden';
|
|
input.name = name;
|
|
input.value = String(value);
|
|
form.appendChild(input);
|
|
}
|