const {HandlebarsApplicationMixin, DocumentSheetV2} = foundry.applications.api const {ActorSheetV2} = foundry.applications.sheets export class MerchantSheet extends HandlebarsApplicationMixin(ActorSheetV2) { /** @inheritDoc */ static DEFAULT_OPTIONS = { position: {width: 520, height: 480}, classes: ['dsa41', 'sheet', 'actor', 'merchant'], tag: 'form', dragDrop: [{ dropSelector: '.tab.inventory.active' }], form: { submitOnChange: true, closeOnSubmit: false, handler: MerchantSheet.#onSubmitForm }, window: { resizable: true, }, actions: { buy: MerchantSheet.#buyWare, editItem: MerchantSheet.#openEmbeddedDocument, editImage: DocumentSheetV2.DEFAULT_OPTIONS.actions.editImage, editServiceImage: MerchantSheet.#editServiceImage, editNewServiceImage: MerchantSheet.#editNewServiceImage, addNewService: MerchantSheet.#addNewService, removeService: MerchantSheet.#removeService, } } static TABS = { sheet: { tabs: [ {id: 'goods', group: 'sheet', label: 'Waren'}, {id: 'services', group: 'sheet', label: 'Dienstleistungen'}, // Meta is added via GM permission ], initial: 'goods' } } /** @inheritDoc */ static PARTS = { form: { template: `systems/DSA_4-1/templates/actor/merchant/main-sheet.hbs` }, goods: { template: `systems/DSA_4-1/templates/actor/merchant/tab-goods.hbs` }, services: { template: `systems/DSA_4-1/templates/actor/merchant/tab-services.hbs` }, meta: { template: `systems/DSA_4-1/templates/actor/merchant/tab-meta.hbs` } } constructor(options = {}) { super(options); } _configureRenderOptions(options) { super._configureRenderOptions(options) if (options.window) { options.window.title = this.document.name } return options } /** * Handle form submission * @this {AdvantageSheet} * @param {SubmitEvent} event * @param {HTMLFormElement} form * @param {FormDataExtended} formData */ static async #onSubmitForm(event, form, formData) { event.preventDefault() await this.document.update(formData.object) // Note: formData.object } static async #buyWare(event, target) { const {itemId} = target.dataset const item = this.document.items.get(itemId) let selections = '' game.actors.filter(p => p.isOwner && p.type === "Character").forEach(actor => { selections += `` }) const actorId = await foundry.applications.api.DialogV2.prompt({ window: {title: `${item.name} kaufen mit wem?`}, content: ``, ok: { label: `Kaufen`, callback: (event, button, dialog) => button.form.elements.actor.value } }); if (actorId) { // ignore the following when dialog was cancelled const actor = game.actors.get(actorId) const canBuy = await actor.reduceWealth(item.system.price) if (canBuy) { // returns false when the wealth cant be reduced sufficiently actor.createEmbeddedDocuments('Item', [item]).then(documents => { documents[0].update({'system.quantity': 1}) }) game.DSA41.socket.executeAsGM("buyFromLootTable", this.document.id, item.id) ChatMessage.create({ user: game.user._id, speaker: {actor}, content: `hat ${item.name} für ${game.DSA41.displayCurrency(item.system.price)} gekauft`, type: CONST.CHAT_MESSAGE_TYPES.IC }) } else { ui.notifications.error(item.name + " ist zu teuer für " + actor.name) } } } /** * * @param {MouseEvent} event */ static #openEmbeddedDocument(event) { let dataset = event.target.dataset if (!dataset.itemId && !dataset.id) { dataset = event.target.parentElement.dataset } const id = dataset.itemId ?? dataset.id if (this.document.isOwner) { // only shop owner can change stock and price this.document.items.get(id).sheet.render(true) } } static async #removeService(event, target) { const {rowId} = target.dataset; const services = this.document.services services.splice(rowId, 1) this.document.update({"system.services": services}) } static async #addNewService(event, target) { event.preventDefault() const fieldset = this.element.querySelector('details') const image = fieldset.querySelector('img').src const name = fieldset.querySelector('input[name="new_name"]').value const price = fieldset.querySelector('input[name="new_price"]').value const availability = fieldset.querySelector('input[name="new_availability"]').value const description = fieldset.querySelector('prose-mirror').value if (name && price) { let services = this.document.system.services services.push({ image, name, price, availability, description }) this.document.update({"system.services": services}).then(e => { this.element.reset() }) } return false } static async #editNewServiceImage(event, target) { const field = target.dataset.field || "img" const current = foundry.utils.getProperty(this.document, field) const fp = new foundry.applications.apps.FilePicker({ type: "image", current: current, callback: (path) => { target.src = path } }) fp.render(true) } static async #editServiceImage(event, target) { const field = target.dataset.field || "img" const current = foundry.utils.getProperty(this.document, field) const fp = new foundry.applications.apps.FilePicker({ type: "image", current: current, callback: (path) => { target.src = path //foundry.utils.setProperty(this.document, field, path) target.parentElement.querySelector(`input[name="${field}"][type="hidden"]`).value = path this.element.submit() } }) fp.render(true) } /** * Handle changing a Document's image. * @param {MouseEvent} event The click event. * @returns {Promise} * @protected */ /* static _onEditImage(event) { const attr = event.currentTarget.dataset.edit; const current = foundry.utils.getProperty(this.object, attr); const { img } = this.document.constructor.getDefaultArtwork?.(this.document.toObject()) ?? {}; const fp = new FilePicker.implementation({ current, type: "image", redirectToRoot: img ? [img] : [], callback: path => { event.target.src = path; event.target.dataset.edit this.document.update({'image': path}) }, top: this.position.top + 40, left: this.position.left + 10 }); return fp.browse(); }*/ _getTabsConfig(group) { const tabs = foundry.utils.deepClone(super._getTabsConfig(group)) // Modify tabs based on document properties if (game.user.isGM) { tabs.tabs.push({id: "meta", group: "sheet", label: "Meta"}) } return tabs } /** @override */ async _prepareContext(options) { const context = await super._prepareContext(options) context.name = this.document.name context.image = this.document.img context.description = this.document.system.description context.goods = this.document.itemTypes["Equipment"] ?? [] context.services = this.document.system.services context.isOwner = this.document.isOwner return context } /** * Actions performed after any render of the Application. * Post-render steps are not awaited by the render process. * @param {ApplicationRenderContext} context Prepared context data * @param {RenderOptions} options Provided render options * @protected */ _onRender(context, options) { new foundry.applications.ux.DragDrop.implementation({ dropSelector: ".window-content", permissions: { drop: this._canDragDrop.bind(this) }, callbacks: { drop: this._onDrop.bind(this) } }).bind(this.element); } _canDragDrop(event, options) { return game.user.isGM } async _onDrop(event) { const data = TextEditor.implementation.getDragEventData(event); const actor = this.actor; const allowed = Hooks.call("dropActorSheetData", actor, this, data); if (allowed === false) return; // Dropped Documents const documentClass = foundry.utils.getDocumentClass(data.type); if (documentClass) { const document = await documentClass.fromDropData(data); if (document.type === "Equipment") { // No duplication by moving items from one actor to another if (document.parent && document.parent !== this.actor) { document.parent.items.get(document._id).delete() } await this._onDropDocument(event, document); } } } }