import AbstractFormInputElement from "./form-element.mjs"; /** * @typedef {import("../forms/fields.mjs").FormInputConfig} FormInputConfig */ /** * @typedef {Object} FilePickerInputConfig * @property {FilePickerOptions.type} [type] * @property {string} [placeholder] * @property {boolean} [noupload] */ /** * A custom HTML element responsible for rendering a file input field and associated FilePicker button. * @extends {AbstractFormInputElement} */ export default class HTMLFilePickerElement extends AbstractFormInputElement { /** @override */ static tagName = "file-picker"; /** * The file path selected. * @type {HTMLInputElement} */ input; /** * A button to open the file picker interface. * @type {HTMLButtonElement} */ button; /** * A reference to the FilePicker application instance originated by this element. * @type {FilePicker} */ picker; /* -------------------------------------------- */ /** * A type of file which can be selected in this field. * @see {@link FilePicker.FILE_TYPES} * @type {FilePickerOptions.type} */ get type() { return this.getAttribute("type") ?? "any"; } set type(value) { if ( !FilePicker.FILE_TYPES.includes(value) ) throw new Error(`Invalid type "${value}" provided which must be a ` + "value in FilePicker.TYPES"); this.setAttribute("type", value); } /* -------------------------------------------- */ /** * Prevent uploading new files as part of this element's FilePicker dialog. * @type {boolean} */ get noupload() { return this.hasAttribute("noupload"); } set noupload(value) { this.toggleAttribute("noupload", value === true); } /* -------------------------------------------- */ /** @override */ _buildElements() { // Initialize existing value this._value ??= this.getAttribute("value") || this.innerText || ""; this.removeAttribute("value"); // Create an input field const elements = []; this.input = this._primaryInput = document.createElement("input"); this.input.className = "image"; this.input.type = "text"; this.input.placeholder = this.getAttribute("placeholder") ?? "path/to/file.ext"; elements.push(this.input); // Disallow browsing for some users if ( game.world && !game.user.can("FILES_BROWSE") ) return elements; // Create a FilePicker button this.button = document.createElement("button"); this.button.className = "fa-solid fa-file-import fa-fw"; this.button.type = "button"; this.button.dataset.tooltip = game.i18n.localize("FILES.BrowseTooltip"); this.button.setAttribute("aria-label", this.button.dataset.tooltip); this.button.tabIndex = -1; elements.push(this.button); return elements; } /* -------------------------------------------- */ /** @override */ _refresh() { this.input.value = this._value; } /* -------------------------------------------- */ /** @override */ _toggleDisabled(disabled) { this.input.disabled = disabled; if ( this.button ) this.button.disabled = disabled; } /* -------------------------------------------- */ /** @override */ _activateListeners() { this.input.addEventListener("input", () => this._value = this.input.value); this.button?.addEventListener("click", this.#onClickButton.bind(this)); } /* -------------------------------------------- */ /** * Handle clicks on the button element to render the FilePicker UI. * @param {PointerEvent} event The initiating click event */ #onClickButton(event) { event.preventDefault(); this.picker = new FilePicker({ type: this.type, current: this.value, allowUpload: !this.noupload, callback: src => this.value = src }); return this.picker.browse(); } /* -------------------------------------------- */ /** * Create a HTMLFilePickerElement using provided configuration data. * @param {FormInputConfig & FilePickerInputConfig} config */ static create(config) { const picker = document.createElement(this.tagName); picker.name = config.name; picker.setAttribute("value", config.value || ""); picker.type = config.type; picker.noupload = config.noupload; foundry.applications.fields.setInputAttributes(picker, config); return picker; } }