From ef31f53ded9395c21ba9705ccff1097025b10d72 Mon Sep 17 00:00:00 2001 From: Haxxer Date: Sat, 29 Jan 2022 12:01:46 +0000 Subject: [PATCH 01/23] Trading interface and app --- languages/en.json | 17 +- scripts/formapplications/itemPileConfig.js | 4 +- scripts/formapplications/itemPileInventory.js | 2 +- scripts/formapplications/tradingApp.js | 308 +++++++++++++++++ scripts/lib/lib.js | 15 +- scripts/module.js | 17 +- scripts/settings.js | 4 +- scripts/socket.js | 12 + styles/module.css | 152 ++++++++- styles/module.css.map | 2 +- styles/module.scss | 310 +++++++++++++----- templates/item-pile-inventory.html | 7 +- templates/trading-app.html | 189 +++++++++++ 13 files changed, 926 insertions(+), 113 deletions(-) create mode 100644 scripts/formapplications/tradingApp.js create mode 100644 templates/trading-app.html diff --git a/languages/en.json b/languages/en.json index 4bca8af2..a43b9301 100644 --- a/languages/en.json +++ b/languages/en.json @@ -6,7 +6,8 @@ "Currency": "Currency", "ContextMenu": { - "ShowToPlayers": "Show Item Pile to players" + "ShowToPlayers": "Item Piles: Show pile to players", + "TradeWith": "Item Piles: Initiate Trade" }, "Inspect": { @@ -28,8 +29,16 @@ "SplitCurrencies": "Split currencies {num_players} ways" }, + "Trade": { + "Prompt": { + "Title": "Trade request", + "Content": "{trader_player_name} ({trader_actor_name}) wants to trade with you ({actor_name}).

Do you accept?" + } + }, + "Errors": { - "DisallowedItemDrop": "You cannot an drop \"{type}\" items", + "DisallowedItemDrop": "You cannot drop \"{type}\" items", + "DisallowedItemTrade": "You cannot trade \"{type}\" items", "NoSourceDrop": "You cannot drop items from the item bar unless you are a GM.", "PileTooFar": "You're too far away to interact with this pile.", "PileLocked": "This item pile is locked and cannot be opened - you can't drop items in it.", @@ -41,6 +50,10 @@ "Title": "Item Type Warning", "Content": "You're dropping an item that is of a type (\"{type}\") that is normally not allowed to be dropped. Are you sure you want to do this?" }, + "TradeTypeWarning": { + "Title": "Item Type Warning", + "Content": "You're attempting to trade an item that is of a type (\"{type}\") that is normally not allowed to be traded. Are you sure you want to do this?" + }, "ResetSettings": { "Title": "Reset Item Piles Module Settings", "Content": "Are you sure you want to reset all of the Item Piles module settings to the current system's defaults? THIS CANNOT BE UNDONE!", diff --git a/scripts/formapplications/itemPileConfig.js b/scripts/formapplications/itemPileConfig.js index d34fa85b..93128074 100644 --- a/scripts/formapplications/itemPileConfig.js +++ b/scripts/formapplications/itemPileConfig.js @@ -72,7 +72,7 @@ export class ItemPileConfig extends FormApplication { if(isLinked){ const doContinue = await Dialog.confirm({ title: game.i18n.localize("ITEM-PILES.Dialogs.LinkedActorWarning.Title"), - content: lib.dialogWarning(game.i18n.localize("ITEM-PILES.Dialogs.LinkedActorWarning.Content")), + content: lib.dialogLayout({ message: game.i18n.localize("ITEM-PILES.Dialogs.LinkedActorWarning.Content") }), defaultYes: false }); if (!doContinue) { @@ -199,7 +199,7 @@ export class ItemPileConfig extends FormApplication { async resetSharingData(){ return new Dialog({ title: game.i18n.localize("ITEM-PILES.Dialogs.ResetSharingData.Title"), - content: lib.dialogWarning(game.i18n.localize("ITEM-PILES.Dialogs.ResetSharingData.Content")), + content: lib.dialogLayout({ message: game.i18n.localize("ITEM-PILES.Dialogs.ResetSharingData.Content") }), buttons: { confirm: { icon: '', diff --git a/scripts/formapplications/itemPileInventory.js b/scripts/formapplications/itemPileInventory.js index 15ce005a..8cbba44d 100644 --- a/scripts/formapplications/itemPileInventory.js +++ b/scripts/formapplications/itemPileInventory.js @@ -34,7 +34,7 @@ export class ItemPileInventory extends FormApplication { static get defaultOptions() { return foundry.utils.mergeObject(super.defaultOptions, { title: game.i18n.localize("ITEM-PILES.Inspect.Title"), - classes: ["sheet", "item-piles-inventory-sheet"], + classes: ["sheet"], template: `${CONSTANTS.PATH}templates/item-pile-inventory.html`, width: 500, height: "auto", diff --git a/scripts/formapplications/tradingApp.js b/scripts/formapplications/tradingApp.js new file mode 100644 index 00000000..662af25c --- /dev/null +++ b/scripts/formapplications/tradingApp.js @@ -0,0 +1,308 @@ +import CONSTANTS from "../constants.js"; +import API from "../api.js"; +import * as lib from "../lib/lib.js"; +import { itemPileSocket, SOCKET_HANDLERS } from "../socket.js"; +import { hotkeyState } from "../hotkeys.js"; +import DropCurrencyDialog from "./dropCurrencyDialog.js"; + +export class TradingApp extends FormApplication { + + constructor(options) { + super(); + this.actor = options.actor; + this.trader = options.trader; + this.traderUserID = options.traderUserID; + + this.actorItems = []; + this.actorCurrencies = []; + } + + + /** @inheritdoc */ + static get defaultOptions() { + return foundry.utils.mergeObject(super.defaultOptions, { + classes: ["dialog", "item-piles-trading-sheet"], + template: `${CONSTANTS.PATH}templates/trading-app.html`, + width: 800, + height: "auto", + dragDrop: [{ dragSelector: null, dropSelector: ".item-piles-item-drop-container" }], + }); + } + + get title(){ + return `Trade between ${this.actor.name} and ${this.trader.name}` + } + + async _onDrop(event) { + + super._onDrop(event); + + let dropData; + try { + dropData = JSON.parse(event.dataTransfer.getData('text/plain')); + } catch (err) { + return false; + } + + if (dropData.type !== "Item") return; + + const itemData = dropData.data; + + if (!dropData.actorId && !game.user.isGM) { + return lib.custom_warning(game.i18n.localize("ITEM-PILES.Errors.NoSourceDrop"), true) + } + + const disallowedType = lib.isItemInvalid(this.trader, itemData); + if (disallowedType) { + if (!game.user.isGM) { + return lib.custom_warning(game.i18n.format("ITEM-PILES.Errors.DisallowedItemTrade", { type: disallowedType }), true) + } + if (!hotkeyState.shiftDown) { + const force = await Dialog.confirm({ + title: game.i18n.localize("ITEM-PILES.Dialogs.TradeTypeWarning.Title"), + content: `

${game.i18n.format("ITEM-PILES.Dialogs.TradeTypeWarning.Content", { type: disallowedType })}

`, + defaultYes: false + }); + if (!force) { + return false; + } + } + } + + this.actorItems.push({ + id: itemData._id, + name: itemData.name, + img: itemData?.img ?? "", + quantity: 1, + maxQuantity: lib.getItemQuantity(itemData) + }); + + this.render(true); + + } + + activateListeners(html) { + super.activateListeners(html); + let self = this; + + html.find('.item-piles-item-row .item-piles-quantity').keyup(function(event){ + + const parent = $(this).closest(".item-piles-item-row"); + + let quantity; + const value = Number($(this).val()); + if(parent.attr('data-type') === "item") { + const itemId = parent.attr("data-item"); + const item = self.actorItems.find(item => item.id === itemId); + quantity = Math.min(value, item.maxQuantity); + }else{ + const currencyPath = parent.attr("data-currency"); + const currency = self.actorCurrencies.find(currency => currency.path === currencyPath); + quantity = Math.min(value, currency.maxQuantity); + } + + $(this).val(quantity); + + if(event.key === "Enter"){ + parent.find(".item-piles-confirm-quantity").click(); + } + + }); + + html.find(".item-piles-quantity-text").dblclick(function(){ + self.resetInputs(); + const parent = $(this).closest(".item-piles-item-row"); + $(this).hide(); + parent.find(".item-piles-quantity-container").show(); + parent.find(".item-piles-confirm-quantity").show(); + const quantityInput = parent.find(".item-piles-quantity"); + quantityInput.focus().select(); + }); + + html.find(".item-piles-confirm-quantity").click(function(){ + const parent = $(this).closest(".item-piles-item-row"); + $(this).hide(); + parent.find(".item-piles-quantity-container").hide(); + parent.find(".item-piles-quantity-text").show(); + const value = Number(parent.find(".item-piles-quantity").val()) + + if(value === 0){ + parent.remove(); + } + + parent.find(".item-piles-quantity-text").text(value); + + if(parent.attr('data-type') === "item"){ + return self.setItemQuantity(parent.attr('data-item'), value) + } + + return self.setCurrencyQuantity(parent.attr('data-currency'), value) + + }) + + html.find(".item-piles-add-currency").click(() => { + this.addCurrency() + }) + + } + + resetInputs(){ + this.element.find(".item-piles-confirm-quantity").each(function(){ + const parent = $(this).closest(".item-piles-item-row"); + $(this).hide(); + parent.find(".item-piles-quantity-container").hide(); + const quantityText = parent.find(".item-piles-quantity-text"); + quantityText.show(); + parent.find(".item-piles-quantity").val(quantityText.text()); + }); + } + + setItemQuantity(itemId, quantity){ + const item = this.actorItems.find(item => item.id === itemId); + item.quantity = quantity; + if(!quantity){ + this.actorItems.splice(this.actorItems.indexOf(item), 1); + } + } + + setCurrencyQuantity(currencyPath, quantity){ + const currency = this.actorCurrencies.find(currency => currency.path === currencyPath); + currency.quantity = quantity; + if(!quantity){ + this.actorCurrencies.splice(this.actorCurrencies.indexOf(quantity), 1); + this.setPosition() + } + } + + async addCurrency(){ + const currencyToAdd = await DropCurrencyDialog.query({ + dropper: this.actor, + itemPile: this.trader + }); + if(!currencyToAdd) return; + + const currencies = lib.getActorCurrencies(this.actor); + + Object.entries(currencyToAdd).forEach(entry => { + + const existingCurrency = this.actorCurrencies.find(currency => currency.path === entry[0]); + + if(existingCurrency){ + existingCurrency.quantity = entry[1]; + return; + } + + const currency = currencies.find(currency => currency.path === entry[0]); + + this.actorCurrencies.push({ + path: entry[0], + quantity: entry[1], + name: currency.name, + img: currency.img, + maxQuantity: currency.quantity + }); + + }); + + this.render(true); + } + + getData(options) { + const data = super.getData(options); + + data.actor = { + name: this.actor.name, + img: this.actor.img, + items: this.actorItems, + currencies: this.actorCurrencies + }; + + data.actor.hasItems = !!data.actor.items.length; + + data.trader = { + name: this.trader.name, + img: this.trader.img, + items: [], + currencies: [] + }; + + return data; + } + + +} + + +export class TradingHandler { + + static async prompt(userId){ + + const uuid = lib.getUuid(target?.actor ?? target) + + const response = await itemPileSocket.executeAsUser(SOCKET_HANDLERS.TRADE_PROMPT, userId, game.user.id, uuid); + + if(!response) return; + + } + + static async _respondPrompt(userId, traderUuid){ + + const tradingUser = game.users.get(userId); + const trader = await fromUuid(traderUuid); + + return new Promise(resolve => { + let resolved = false; + const dialog = new Dialog({ + title: game.i18n.localize("ITEM-PILES.Trade.Prompt.Title"), + content: lib.dialogLayout({ + icon: "fas fa-handshake", + title: "Trade Request", + message: game.i18n.format("ITEM-PILES.Trade.Prompt.Content", { + trader_player_name: tradingUser.name, + trader_actor_name: trader.name, + actor_name: game.user.character.name + }), + extraHtml: ` +
+ +
` + }), + buttons: { + confirm: { + icon: '', + label: game.i18n.localize("Yes"), + callback: () => { + resolved = true; + resolve(true) + } + }, + cancel: { + icon: '', + label: game.i18n.localize("No"), + callback: () => { + resolved = true; + resolve(false); + } + } + }, + default: "cancel", + render: (html) => { + const progressBar = html.find(".progress-bar"); + progressBar.css("transition", 'width 30s linear') + progressBar.css("width", "100%") + } + }) + + setTimeout(() => { + if(resolved) return; + lib.custom_warning("You did not respond to the trade request quickly enough, and thus auto-declined it.") + dialog.close(); + }, 30500) + + dialog.render(true); + }) + + } + + +} \ No newline at end of file diff --git a/scripts/lib/lib.js b/scripts/lib/lib.js index 1a69bbb2..fb019634 100644 --- a/scripts/lib/lib.js +++ b/scripts/lib/lib.js @@ -136,12 +136,15 @@ export function is_real_number(inNumber) { && isFinite(inNumber); } -export function dialogWarning(message, icon = "fas fa-exclamation-triangle"){ - return `

-

- Item Piles -

${message} -

`; +export function dialogLayout({ title="Item Piles", message, icon = "fas fa-exclamation-triangle", extraHtml = "" }={}){ + return ` +
+

+

${title}

+

${message}

+ ${extraHtml} +
+ `; } diff --git a/scripts/module.js b/scripts/module.js index c05b5c5f..dfaefc1e 100644 --- a/scripts/module.js +++ b/scripts/module.js @@ -11,9 +11,6 @@ import { registerSocket } from "./socket.js"; import { registerLibwrappers } from "./libwrapper.js"; import { registerHotkeysPre, registerHotkeysPost } from "./hotkeys.js"; import flagManager from "./flagManager.js"; -import { ItemPileInventory } from "./formapplications/itemPileInventory.js"; -import DropCurrencyDialog from "./formapplications/dropCurrencyDialog.js"; -import { ItemPileCurrenciesEditor } from "./formapplications/itemPileCurrenciesEditor.js"; import { getActorCurrencies, getActorItems } from "./lib/lib.js"; Hooks.once("init", async () => { @@ -85,6 +82,8 @@ Hooks.once("ready", async () => { migrateSettings(); Hooks.callAll(HOOKS.READY); + + }); const debounceManager = { @@ -230,6 +229,7 @@ const module = { }, _handleActorContextMenu(html, menuItems) { + menuItems.push({ name: "ITEM-PILES.ContextMenu.ShowToPlayers", icon: ``, @@ -242,7 +242,16 @@ const module = { condition: (html) => { const actorId = html[0].dataset.documentId; const actor = game.actors.get(actorId); - return API.isValidItemPile(actor); + return game.user.isGM && API.isValidItemPile(actor); + } + }, { + name: "ITEM-PILES.ContextMenu.TradeWith", + icon: ``, + callback: (html) => { + return true; + }, + condition: (html) => { + return true; } }); } diff --git a/scripts/settings.js b/scripts/settings.js index ae050f95..fa8303b8 100644 --- a/scripts/settings.js +++ b/scripts/settings.js @@ -244,7 +244,7 @@ export async function checkSystem(){ return Dialog.prompt({ title: game.i18n.localize("ITEM-PILES.Dialogs.NoSystemFound.Title"), - content: lib.dialogWarning(game.i18n.localize("ITEM-PILES.Dialogs.NoSystemFound.Content")), + content: lib.dialogLayout({ message: game.i18n.localize("ITEM-PILES.Dialogs.NoSystemFound.Content") }), callback: () => {} }); @@ -258,7 +258,7 @@ export async function checkSystem(){ return new Dialog({ title: game.i18n.localize("ITEM-PILES.Dialogs.SystemFound.Title"), - content: lib.dialogWarning(game.i18n.localize("ITEM-PILES.Dialogs.SystemFound.Content"), "fas fa-search"), + content: lib.dialogLayout({ message: game.i18n.localize("ITEM-PILES.Dialogs.SystemFound.Content"), icon: "fas fa-search" }), buttons: { confirm: { icon: '', diff --git a/scripts/socket.js b/scripts/socket.js index d594ab91..76b61b1b 100644 --- a/scripts/socket.js +++ b/scripts/socket.js @@ -4,6 +4,7 @@ import API from "./api.js"; import { ItemPileInventory } from "./formapplications/itemPileInventory.js"; import chatHandler from "./chathandler.js"; import flagManager from "./flagManager.js"; +import { TradingHandler } from "./formapplications/tradingApp.js"; export const SOCKET_HANDLERS = { /** @@ -52,6 +53,12 @@ export const SOCKET_HANDLERS = { TRANSFER_ATTRIBUTES: "transferAttributes", TRANSFER_ALL_ATTRIBUTES: "transferAllAttributes", TRANSFER_EVERYTHING: "transferEverything", + + /** + * Trading sockets + */ + TRADE_PROMPT: "tradePrompt", + TRADE_ACCEPTED: "tradeAccepted", }; export let itemPileSocket; @@ -105,6 +112,11 @@ export function registerSocket() { itemPileSocket.register(SOCKET_HANDLERS.TRANSFER_ATTRIBUTES, (...args) => API._transferAttributes(...args)) itemPileSocket.register(SOCKET_HANDLERS.TRANSFER_ALL_ATTRIBUTES, (...args) => API._transferAllAttributes(...args)) itemPileSocket.register(SOCKET_HANDLERS.TRANSFER_EVERYTHING, (...args) => API._transferEverything(...args)) + + /** + * Trading sockets + */ + itemPileSocket.register(SOCKET_HANDLERS.TRADE_PROMPT, (...args) => TradingHandler._respondPrompt(...args)) } async function callHook(inHookName, ...args) { diff --git a/styles/module.css b/styles/module.css index 1c2e33c4..b2140b13 100644 --- a/styles/module.css +++ b/styles/module.css @@ -1,4 +1,18 @@ /* ----------------------- Generic ----------------------- */ +.item-piles-progress { + width: calc(100% - 10px); + height: 6px; + background: #e1e4e8; + border-radius: 3px; + overflow: hidden; + margin: 0 5px; +} +.item-piles-progress .progress-bar { + display: block; + height: 100%; + background-color: rgba(0, 0, 0, 0.4); + transition: width 30s linear; +} .item-piles-dialog { text-align: center; font-size: 0.9rem; @@ -10,6 +24,15 @@ .item-piles-clickable { cursor: pointer; } +.item-piles-clickable-red { + color: red; +} +.item-piles-clickable-green { + color: green; +} +.item-piles-clickable-green:hover { + text-shadow: 0 0 8px #007d00; +} .item-piles-moveable { cursor: move; } @@ -53,6 +76,11 @@ margin-bottom: 0.5rem; padding-bottom: 0.5rem; } +.item-piles-top-divider { + border-top: 1px solid rgba(0, 0, 0, 0.35); + margin-top: 0.5rem; + padding-top: 0.5rem; +} .item-piles-disabled { background-color: var(--color-bg-btn-minor-inactive); } @@ -64,80 +92,81 @@ border-radius: 4px; } -/* ----------------------- INVENTORY ----------------------- */ -.item-piles-inventory-sheet #item-piles-preview-container { +#item-piles-preview-container { position: absolute; display: none; } -.item-piles-inventory-sheet #item-piles-preview-container #item-piles-preview-image { +#item-piles-preview-container #item-piles-preview-image { border: 0; width: 300px; border-radius: 1rem; box-shadow: rgba(0, 0, 0, 0.25) 0px 54px 55px, rgba(0, 0, 0, 0.12) 0px -12px 30px, rgba(0, 0, 0, 0.12) 0px 4px 6px, rgba(0, 0, 0, 0.17) 0px 12px 13px, rgba(0, 0, 0, 0.09) 0px -3px 5px; } -.item-piles-inventory-sheet .item-piles-items { + +/* ----------------------- INVENTORY ----------------------- */ +.item-piles-items-list { max-height: 500px; overflow-y: scroll; } -.item-piles-inventory-sheet .item-piles-items .item-piles-add-currency { +.item-piles-items-list .item-piles-add-currency { margin-right: 5px; flex: 0 1 auto; vertical-align: middle; } -.item-piles-inventory-sheet .item-piles-items .item-piles-item-row { +.item-piles-items-list .item-piles-item-row { padding: 0 5px 0 0; margin-bottom: 0.25rem; margin-right: 5px; border-radius: 4px; } -.item-piles-inventory-sheet .item-piles-items .item-piles-item-row:nth-child(even):not(.item-piles-disabled) { +.item-piles-items-list .item-piles-item-row:nth-child(even):not(.item-piles-disabled) { background-color: var(--color-text-light-highlight); } -.item-piles-inventory-sheet .item-piles-items .item-piles-item-row .item-piles-disabled { +.item-piles-items-list .item-piles-item-row .item-piles-disabled { background-color: var(--color-bg-btn-minor-inactive); } -.item-piles-inventory-sheet .item-piles-items .item-piles-item-row div { +.item-piles-items-list .item-piles-item-row div { display: inline-flex; flex-direction: column; justify-content: center; } -.item-piles-inventory-sheet .item-piles-items .item-piles-item-row .item-piles-text { +.item-piles-items-list .item-piles-item-row .item-piles-text { line-height: 1.6rem; font-size: 1rem; text-wrap: normal; flex: 0 1 auto; } -.item-piles-inventory-sheet .item-piles-items .item-piles-item-row .item-piles-name { +.item-piles-items-list .item-piles-item-row .item-piles-name { padding: 0 0.5rem; flex: 4; display: inline-flex; flex-direction: column; } -.item-piles-inventory-sheet .item-piles-items .item-piles-item-row .item-piles-name .item-piles-name-container { +.item-piles-items-list .item-piles-item-row .item-piles-name .item-piles-name-container { flex: 1; display: inline-flex; flex-direction: column; justify-content: center; } -.item-piles-inventory-sheet .item-piles-items .item-piles-item-row .item-piles-name .item-piles-name-container a { +.item-piles-items-list .item-piles-item-row .item-piles-name .item-piles-name-container a { max-width: fit-content; } -.item-piles-inventory-sheet .item-piles-items .item-piles-item-row .item-piles-name span { +.item-piles-items-list .item-piles-item-row .item-piles-name span { line-height: 1; flex: 0; } -.item-piles-inventory-sheet .item-piles-items .item-piles-item-row .item-piles-quantity { +.item-piles-items-list .item-piles-item-row .item-piles-quantity { flex: 1; margin-left: 0.5rem; text-align: right; } -.item-piles-inventory-sheet .item-piles-items .item-piles-item-row .item-piles-input-divider { +.item-piles-items-list .item-piles-item-row .item-piles-input-divider { flex: 1; margin: 0.1rem 0.5rem 0 0.25rem; font-size: 0.8rem; line-height: 1.5rem; } -.item-piles-inventory-sheet .item-piles-items .item-piles-item-row .item-piles-item-take-button, .item-piles-inventory-sheet .item-piles-items .item-piles-item-row .item-piles-currency-take-button { +.item-piles-items-list .item-piles-item-row .item-piles-item-take-button, .item-piles-items-list .item-piles-item-row .item-piles-currency-take-button { flex: 0; min-width: 4rem; height: 26px; @@ -253,4 +282,93 @@ padding-right: 2px; } +/* ---------------------- TRADING UI ---------------------- */ +.item-piles-trading-sheet .item-piles-item-row { + padding: 2px; +} +.item-piles-trading-sheet .item-piles-remove-item { + opacity: 0.5; +} +.item-piles-trading-sheet .item-piles-remove-item:hover { + opacity: 1; +} +.item-piles-trading-sheet .item-piles-confirm-quantity { + display: none; +} +.item-piles-trading-sheet .item-piles-quantity-text { + line-height: 1.6rem; + font-size: 0.85rem; + background-color: rgba(255, 255, 255, 0.15); + border: 1px solid rgba(0, 0, 0, 0.15); + padding-right: 5px; + border-radius: 4px; + margin-left: 10px; +} +.item-piles-trading-sheet .item-piles-quantity-text:hover { + background-color: rgba(255, 255, 255, 0.5); + border: 1px solid rgba(0, 0, 0, 0.5); +} +.item-piles-trading-sheet .item-piles-add-currency { + flex: 1; + vertical-align: middle; + margin-bottom: 5px; +} +.item-piles-trading-sheet .item-piles-items-list { + min-height: 300px; + max-height: 300px; +} +.item-piles-trading-sheet .item-piles-currency-list { + min-height: unset; + max-height: unset; +} +.item-piles-trading-sheet .item-piles-img-container { + min-height: 29px; + max-width: 29px; + max-height: 29px; + overflow: hidden; + border-radius: 4px; + border: 1px solid black; +} +.item-piles-trading-sheet .item-piles-img-container .item-piles-img { + border: 0; + width: auto; + height: 100%; + transition: transform 250ms; +} +.item-piles-trading-sheet .item-piles-img-container .item-piles-img:hover { + transform: scale(1.25, 1.25); +} +.item-piles-trading-sheet .accepted { + box-shadow: 0 0 40px rgba(0, 255, 0, 0.35) inset, 0 0 10px rgba(0, 255, 0, 0.35); + transition: all 0.6s cubic-bezier(0.165, 0.84, 0.44, 1); +} +.item-piles-trading-sheet h1, .item-piles-trading-sheet h2 { + border: 0; + vertical-align: center; +} +.item-piles-trading-sheet .character-header { + display: inline-flex; + flex-direction: row; + flex: 0 1 auto; + padding: 5px; +} +.item-piles-trading-sheet .character-header .character-name { + padding-top: 0.5rem; + text-align: center; + flex: 1; +} +.item-piles-trading-sheet .character-header img { + max-width: 40px; + max-height: 40px; + width: auto; + height: auto; + border: 0; + margin-right: auto; + flex: 0 1 auto; + border-radius: 5px; +} +.item-piles-trading-sheet .character-header.trader img { + margin-left: auto; +} + /*# sourceMappingURL=module.css.map */ diff --git a/styles/module.css.map b/styles/module.css.map index bef765b0..af37fab0 100644 --- a/styles/module.css.map +++ b/styles/module.css.map @@ -1 +1 @@ -{"version":3,"sourceRoot":"","sources":["module.scss"],"names":[],"mappings":"AAAA;AAIE;EACE;EACA;EACA;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAIA;EACE;;AAEF;EACE;;AAIJ;EACE;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EAEA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAKN;EACE;;AAGF;EACE;EACA;EACA;;AAGF;EACE;;;AAKJ;EACE;EACA;EACA;EACA;;;AAGF;AAIE;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAIJ;EACE;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAIJ;EACE;EACA;;AAIJ;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;;AASR;AAEA;EACE;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAKA;EACE;EACA;EACA;;AAGF;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAIJ;EACE;;AAEA;EACE;EACA;EACA;EACA;;AAKN;EACE;EACA;EACA;;;AAON;AAEA;EACE;;AAEA;EACE;EACA;;AAIA;EACE;EACA;;AAGF;EACE;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA","file":"module.css"} \ No newline at end of file +{"version":3,"sourceRoot":"","sources":["module.scss"],"names":[],"mappings":"AAAA;AAIE;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EAEA;;AAKJ;EACE;EACA;EACA;;AAGF;EACE;;AAGF;EACE;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE;;AAIJ;EACE;;AAGF;EACE;;AAIA;EACE;;AAEF;EACE;;AAIJ;EACE;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EAEA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAKN;EACE;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;;;AAKJ;EACE;EACA;EACA;EACA;;;AAGF;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;;;AAIJ;AAGA;EACE;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAIJ;EACE;EACA;;AAIJ;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;;AAQN;AAEA;EACE;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAKA;EACE;EACA;EACA;;AAGF;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAIJ;EACE;;AAEA;EACE;EACA;EACA;EACA;;AAKN;EACE;EACA;EACA;;;AAON;AAEA;EACE;;AAEA;EACE;EACA;;AAIA;EACE;EACA;;AAGF;EACE;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;;;AAKN;AAKE;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAKA;EACE;EACA;EACA;EAEA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;;AASR;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EAEE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAIA;EACE","file":"module.css"} \ No newline at end of file diff --git a/styles/module.scss b/styles/module.scss index 9c58d8c9..1954a536 100644 --- a/styles/module.scss +++ b/styles/module.scss @@ -2,6 +2,24 @@ .item-piles { + &-progress { + width: calc(100% - 10px); + height: 6px; + background: #e1e4e8; + border-radius: 3px; + overflow: hidden; + margin: 0 5px; + + .progress-bar { + display: block; + height: 100%; + background-color: rgba(0,0,0, 0.4); + + transition: width 30s linear; + } + } + + &-dialog { text-align: center; font-size: 0.9rem; @@ -14,6 +32,18 @@ &-clickable{ cursor: pointer; + + &-red{ + color:red; + } + + &-green{ + color:green; + } + + &-green:hover{ + text-shadow: 0 0 8px rgba(0, 125, 0, 1); + } } &-moveable{ @@ -72,6 +102,12 @@ padding-bottom: 0.5rem; } + &-top-divider{ + border-top: 1px solid rgba(0,0,0,0.35); + margin-top: 0.5rem; + padding-top: 0.5rem; + } + &-disabled { background-color: var(--color-bg-btn-minor-inactive) } @@ -85,109 +121,107 @@ border-radius: 4px; } +#item-piles-preview-container{ + position: absolute; + display: none; + + #item-piles-preview-image{ + border: 0; + width: 300px; + border-radius: 1rem; + box-shadow: rgba(0, 0, 0, 0.25) 0px 54px 55px, rgba(0, 0, 0, 0.12) 0px -12px 30px, rgba(0, 0, 0, 0.12) 0px 4px 6px, rgba(0, 0, 0, 0.17) 0px 12px 13px, rgba(0, 0, 0, 0.09) 0px -3px 5px; + } +} + /* ----------------------- INVENTORY ----------------------- */ -.item-piles-inventory-sheet{ - #item-piles-preview-container{ - position: absolute; - display: none; +.item-piles-items-list { + max-height: 500px; + overflow-y: scroll; - #item-piles-preview-image{ - border: 0; - width: 300px; - border-radius: 1rem; - box-shadow: rgba(0, 0, 0, 0.25) 0px 54px 55px, rgba(0, 0, 0, 0.12) 0px -12px 30px, rgba(0, 0, 0, 0.12) 0px 4px 6px, rgba(0, 0, 0, 0.17) 0px 12px 13px, rgba(0, 0, 0, 0.09) 0px -3px 5px; - } + .item-piles-add-currency { + margin-right: 5px; + flex: 0 1 auto; + vertical-align: middle; } - .item-piles-items { - max-height: 500px; - overflow-y: scroll; + .item-piles-item-row { + padding: 0 5px 0 0; + margin-bottom: 0.25rem; + margin-right: 5px; + border-radius: 4px; - .item-piles-add-currency { - margin-right: 5px; - flex: 0 1 auto; - vertical-align: middle; + &:nth-child(even):not(.item-piles-disabled) { + background-color: var(--color-text-light-highlight); } - .item-piles-item-row { - padding: 0 5px 0 0; - margin-bottom: 0.25rem; - margin-right: 5px; - border-radius: 4px; - - &:nth-child(even):not(.item-piles-disabled) { - background-color: var(--color-text-light-highlight); - } + .item-piles-disabled { + background-color: var(--color-bg-btn-minor-inactive) + } - .item-piles-disabled { - background-color: var(--color-bg-btn-minor-inactive) - } + div { + display: inline-flex; + flex-direction: column; + justify-content: center; + } - div { - display: inline-flex; - flex-direction: column; - justify-content: center; - } + .item-piles-text { + line-height: 1.6rem; + font-size: 1rem; + text-wrap: normal; + flex: 0 1 auto; + } - .item-piles-text { - line-height: 1.6rem; - font-size: 1rem; - text-wrap: normal; - flex: 0 1 auto; - } + .item-piles-name{ + padding: 0 0.5rem; + flex: 4; + display: inline-flex; + flex-direction: column; - .item-piles-name{ - padding: 0 0.5rem; - flex: 4; + .item-piles-name-container{ + flex:1; display: inline-flex; flex-direction: column; + justify-content: center; - .item-piles-name-container{ - flex:1; - display: inline-flex; - flex-direction: column; - justify-content: center; - - a { - max-width: fit-content; - } - } - - span { - line-height: 1; - flex:0; + a { + max-width: fit-content; } } - .item-piles-quantity{ - flex: 1; - margin-left: 0.5rem; - text-align:right; + span { + line-height: 1; + flex:0; } + } - .item-piles-input-divider{ - flex: 1; - margin: 0.1rem 0.5rem 0 0.25rem; - font-size: 0.8rem; - line-height: 1.5rem; - } + .item-piles-quantity{ + flex: 1; + margin-left: 0.5rem; + text-align:right; + } - .item-piles-item-take-button, .item-piles-currency-take-button{ - flex: 0; - min-width: 4rem; - height: 26px; - padding: 1px 3px; - line-height: inherit; - } + .item-piles-input-divider{ + flex: 1; + margin: 0.1rem 0.5rem 0 0.25rem; + font-size: 0.8rem; + line-height: 1.5rem; + } + .item-piles-item-take-button, .item-piles-currency-take-button{ + flex: 0; + min-width: 4rem; + height: 26px; + padding: 1px 3px; + line-height: inherit; } } } + /* ----------------------- CONFIG ----------------------- */ .item-piles-config { @@ -324,3 +358,131 @@ } } +/* ---------------------- TRADING UI ---------------------- */ + + +.item-piles-trading-sheet{ + + .item-piles-item-row{ + padding: 2px; + } + + .item-piles-remove-item{ + opacity: 0.5; + } + + .item-piles-remove-item:hover{ + opacity: 1; + } + + .item-piles-confirm-quantity{ + display:none; + } + + .item-piles-quantity-text{ + line-height: 1.6rem; + font-size: 0.85rem; + background-color: rgba(255, 255, 255, 0.15); + border: 1px solid rgba(0, 0, 0, 0.15); + padding-right: 5px; + border-radius: 4px; + margin-left: 10px; + } + + .item-piles-quantity-text:hover{ + background-color: rgba(255, 255, 255, 0.5); + border: 1px solid rgba(0, 0, 0, 0.5); + } + + .item-piles-add-currency { + flex: 1; + vertical-align: middle; + margin-bottom: 5px; + } + + .item-piles-items-list { + min-height: 300px; + max-height: 300px; + } + + .item-piles-currency-list { + min-height: unset; + max-height: unset; + } + + .item-piles { + + &-img-container{ + min-height: 29px; + max-width: 29px; + max-height: 29px; + + overflow: hidden; + border-radius: 4px; + border: 1px solid black; + + .item-piles-img{ + border: 0; + width: auto; + height: 100%; + transition: transform 250ms; + + &:hover { + transform: scale(1.25,1.25); + } + } + + + } + + } + + .accepted { + box-shadow: 0 0 40px rgba(0, 255, 0, 0.35) inset, 0 0 10px rgba(0, 255, 0, 0.35); + transition: all 0.6s cubic-bezier(0.165, 0.84, 0.44, 1); + } + + h1, h2 { + border: 0; + vertical-align: center; + } + + .character-header{ + + display: inline-flex; + flex-direction: row; + flex: 0 1 auto; + padding: 5px; + + .character-name{ + padding-top:0.5rem; + text-align: center; + flex: 1; + } + + img { + max-width: 40px; + max-height: 40px; + width: auto; + height: auto; + border: 0; + margin-right: auto; + flex: 0 1 auto; + border-radius: 5px; + } + + &.trader{ + img { + margin-left: auto; + } + } + + } + +} + + + + + + diff --git a/templates/item-pile-inventory.html b/templates/item-pile-inventory.html index d3aa15d8..5d968a00 100644 --- a/templates/item-pile-inventory.html +++ b/templates/item-pile-inventory.html @@ -2,7 +2,6 @@
- {{#if isDeleted}}

{{localize "ITEM-PILES.Inspect.Destroyed"}}

@@ -25,7 +24,7 @@
-
+
{{#if hasItems}}

{{localize "ITEM-PILES.Items"}}:

@@ -133,11 +132,11 @@

{{localize "ITEM-PILES.Currencies"}}:

{{#if @root.hasRecipient}} {{#unless hasCurrencies}}
-
+
{{localize "ITEM-PILES.Inspect.AddCurrency"}} -
+
{{/unless}} {{/if}}
diff --git a/templates/trading-app.html b/templates/trading-app.html new file mode 100644 index 00000000..9dd72621 --- /dev/null +++ b/templates/trading-app.html @@ -0,0 +1,189 @@ +
+ + + +
+ +
+ +
+ +

{{ actor.name }}

+
+ +
+ +
+ + {{#unless actor.hasItems}} +
+

Drag & drop items to begin trading

+
+ {{/unless}} + + {{#each actor.items as |item id|}} + +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ +
+ {{item.quantity}} + +
+
+ + {{/each}} + +
+ +
+ + + + {{#each actor.currencies as |currency id|}} + +
+ +
+ +
+ +
+ + + +
+ +
+ +
+ {{currency.quantity}} + +
+
+ + {{/each}} + +
+ +
+ +
+ + + +
+ +
+

{{ trader.name }}

+ +
+ +
+ +
+ + {{#each trader.items as |item id|}} + +
+ +
+ +
+ +
+ +
+ {{item.quantity}} +
+
+ + {{/each}} + +
+ +
+ +
+
+ + {{#each trader.currencies as |currency id|}} + +
+ +
+ + + +
+ {{currency.quantity}} +
+
+ + {{/each}} + +
+ +
+ +
+
+ +
From 2657a324380baa978b9ea8a83053bdc41394e20c Mon Sep 17 00:00:00 2001 From: Haxxer Date: Mon, 31 Jan 2022 23:46:27 +0000 Subject: [PATCH 02/23] Fix scss --- styles/module.css | 7 +++++++ styles/module.css.map | 2 +- styles/module.scss | 33 +++++++++++++++++---------------- 3 files changed, 25 insertions(+), 17 deletions(-) diff --git a/styles/module.css b/styles/module.css index c8292d61..f63b9b66 100644 --- a/styles/module.css +++ b/styles/module.css @@ -112,6 +112,13 @@ max-height: 500px; overflow-y: scroll; } +.item-piles-items-list .item-piles-change-actor-select { + display: none; +} +.item-piles-items-list #item-piles-preview-container { + position: absolute; + display: none; +} .item-piles-items-list .item-piles-add-currency { margin-right: 5px; flex: 0 1 auto; diff --git a/styles/module.css.map b/styles/module.css.map index a346d8c7..0a7f6b6a 100644 --- a/styles/module.css.map +++ b/styles/module.css.map @@ -1 +1 @@ -{"version":3,"sourceRoot":"","sources":["module.scss"],"names":[],"mappings":"AAAA;AAIE;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EAEA;;AAKJ;EACE;EACA;EACA;;AAGF;EACE;;AAGF;EACE;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE;;AAIJ;EACE;;AAGF;EACE;EACA;;AAGF;EACE;;AAIA;EACE;;AAEF;EACE;;AAIJ;EACE;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EAEA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAKN;EACE;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;;;AAKJ;EACE;EACA;EACA;EACA;;;AAGF;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;;;AAIJ;AAGA;EACE;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAIJ;EACE;EACA;;AAIJ;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;;AAQN;AAEA;EACE;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAKA;EACE;EACA;EACA;;AAGF;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAIJ;EACE;;AAEA;EACE;EACA;EACA;EACA;;AAKN;EACE;EACA;EACA;;;AAON;AAEA;EACE;;AAEA;EACE;EACA;;AAIA;EACE;EACA;;AAGF;EACE;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;;;AAKN;AAKE;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAKA;EACE;EACA;EACA;EAEA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;;AASR;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EAEE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAIA;EACE","file":"module.css"} \ No newline at end of file +{"version":3,"sourceRoot":"","sources":["module.scss"],"names":[],"mappings":"AAAA;AAIE;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EAEA;;AAKJ;EACE;EACA;EACA;;AAGF;EACE;;AAGF;EACE;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE;;AAIJ;EACE;;AAGF;EACE;EACA;;AAGF;EACE;;AAIA;EACE;;AAEF;EACE;;AAIJ;EACE;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EAEA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAKN;EACE;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;;;AAKJ;EACE;EACA;EACA;EACA;;;AAGF;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;;;AAIJ;AAGA;EACE;EACA;;AAEA;EACE;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAIJ;EACE;EACA;;AAIJ;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;;AAQN;AAEA;EACE;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAKA;EACE;EACA;EACA;;AAGF;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAIJ;EACE;;AAEA;EACE;EACA;EACA;EACA;;AAKN;EACE;EACA;EACA;;;AAON;AAEA;EACE;;AAEA;EACE;EACA;;AAIA;EACE;EACA;;AAGF;EACE;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;;;AAKN;AAKE;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAKA;EACE;EACA;EACA;EAEA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;;AASR;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EAEE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAIA;EACE","file":"module.css"} \ No newline at end of file diff --git a/styles/module.scss b/styles/module.scss index 792ce58c..acb9184e 100644 --- a/styles/module.scss +++ b/styles/module.scss @@ -149,9 +149,10 @@ display:none; } - #item-piles-preview-container{ + #item-piles-preview-container { position: absolute; display: none; + } .item-piles-add-currency { margin-right: 5px; @@ -374,25 +375,25 @@ /* ---------------------- TRADING UI ---------------------- */ -.item-piles-trading-sheet{ +.item-piles-trading-sheet { - .item-piles-item-row{ + .item-piles-item-row { padding: 2px; } - .item-piles-remove-item{ + .item-piles-remove-item { opacity: 0.5; } - .item-piles-remove-item:hover{ + .item-piles-remove-item:hover { opacity: 1; } - .item-piles-confirm-quantity{ - display:none; + .item-piles-confirm-quantity { + display: none; } - .item-piles-quantity-text{ + .item-piles-quantity-text { line-height: 1.6rem; font-size: 0.85rem; background-color: rgba(255, 255, 255, 0.15); @@ -402,7 +403,7 @@ margin-left: 10px; } - .item-piles-quantity-text:hover{ + .item-piles-quantity-text:hover { background-color: rgba(255, 255, 255, 0.5); border: 1px solid rgba(0, 0, 0, 0.5); } @@ -425,7 +426,7 @@ .item-piles { - &-img-container{ + &-img-container { min-height: 29px; max-width: 29px; max-height: 29px; @@ -434,14 +435,14 @@ border-radius: 4px; border: 1px solid black; - .item-piles-img{ + .item-piles-img { border: 0; width: auto; height: 100%; transition: transform 250ms; &:hover { - transform: scale(1.25,1.25); + transform: scale(1.25, 1.25); } } @@ -460,15 +461,15 @@ vertical-align: center; } - .character-header{ + .character-header { display: inline-flex; flex-direction: row; flex: 0 1 auto; padding: 5px; - .character-name{ - padding-top:0.5rem; + .character-name { + padding-top: 0.5rem; text-align: center; flex: 1; } @@ -484,7 +485,7 @@ border-radius: 5px; } - &.trader{ + &.trader { img { margin-left: auto; } From b695773ff8b721872ad32de5d28e353821f0dee4 Mon Sep 17 00:00:00 2001 From: Haxxer Date: Tue, 1 Feb 2022 19:11:14 +0000 Subject: [PATCH 03/23] Minor changes to the trade request --- languages/en.json | 2 +- scripts/formapplications/tradingApp.js | 43 +++++++++++++++++++++----- scripts/module.js | 4 ++- 3 files changed, 40 insertions(+), 9 deletions(-) diff --git a/languages/en.json b/languages/en.json index 812daf4c..445a450f 100644 --- a/languages/en.json +++ b/languages/en.json @@ -32,7 +32,7 @@ "Trade": { "Prompt": { "Title": "Trade request", - "Content": "{trader_player_name} ({trader_actor_name}) wants to trade with you ({actor_name}).

Do you accept?" + "Content": "{trader_player_name} (as {trader_actor_name}) wants to trade with you ({actor_name}).

Do you accept?" } }, diff --git a/scripts/formapplications/tradingApp.js b/scripts/formapplications/tradingApp.js index 662af25c..5be64698 100644 --- a/scripts/formapplications/tradingApp.js +++ b/scripts/formapplications/tradingApp.js @@ -235,11 +235,34 @@ export class TradingApp extends FormApplication { export class TradingHandler { - static async prompt(userId){ + static async prompt(){ - const uuid = lib.getUuid(target?.actor ?? target) + let content = `

Pick which player you want to trade with, and which actor represents you in the trade:

`; - const response = await itemPileSocket.executeAsUser(SOCKET_HANDLERS.TRADE_PROMPT, userId, game.user.id, uuid); + const users = game.users.filter(user => user.active && user !== game.user).map(user => { + return ``; + }); + + const actors = game.actors.filter(actor => actor.isOwner && actor.data.token.actorLink).map(actor => { + return ``; + }); + + content += `
` + content += `
` + + const [userId, actorUuid] = await Dialog.prompt({ + title: "Trading Request: Pick an actor", + content: content, + callback: (html) => { + const userId = html.find('select[name="user"]').val() + const actorUuid = html.find('select[name="actor"]').val() + return [userId, actorUuid] + } + }) + + if(!actorUuid) return false; + + const response = await itemPileSocket.executeAsUser(SOCKET_HANDLERS.TRADE_PROMPT, userId, game.user.id, actorUuid); if(!response) return; @@ -250,7 +273,7 @@ export class TradingHandler { const tradingUser = game.users.get(userId); const trader = await fromUuid(traderUuid); - return new Promise(resolve => { + return await new Promise(resolve => { let resolved = false; const dialog = new Dialog({ title: game.i18n.localize("ITEM-PILES.Trade.Prompt.Title"), @@ -287,9 +310,14 @@ export class TradingHandler { }, default: "cancel", render: (html) => { + const progressBarContainer = html.find(".item-piles-progress"); const progressBar = html.find(".progress-bar"); - progressBar.css("transition", 'width 30s linear') - progressBar.css("width", "100%") + progressBarContainer.css("opacity", "0"); + setTimeout(() => { + progressBarContainer.fadeTo(1, 1000) + progressBar.css("transition", 'width 20s linear') + progressBar.css("width", "100%") + }, 14000); } }) @@ -297,7 +325,8 @@ export class TradingHandler { if(resolved) return; lib.custom_warning("You did not respond to the trade request quickly enough, and thus auto-declined it.") dialog.close(); - }, 30500) + resolve(false); + }, 35000) dialog.render(true); }) diff --git a/scripts/module.js b/scripts/module.js index f4a76b00..39cdfd7f 100644 --- a/scripts/module.js +++ b/scripts/module.js @@ -12,6 +12,7 @@ import { registerSocket } from "./socket.js"; import { registerLibwrappers } from "./libwrapper.js"; import { registerHotkeysPre, registerHotkeysPost } from "./hotkeys.js"; import { getActorCurrencies, getActorItems } from "./lib/lib.js"; +import { TradingHandler } from "./formapplications/tradingApp.js"; Hooks.once("init", async () => { @@ -53,7 +54,8 @@ Hooks.once("init", async () => { } window.ItemPiles = { - API + API, + TradingHandler } }); From 2a4f6aa2aeaccf116212d278046a6c17a029fe69 Mon Sep 17 00:00:00 2001 From: Haxxer Date: Tue, 1 Feb 2022 21:18:22 +0000 Subject: [PATCH 04/23] Chat commands --- scripts/module.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/scripts/module.js b/scripts/module.js index 39cdfd7f..8465b12d 100644 --- a/scripts/module.js +++ b/scripts/module.js @@ -22,6 +22,7 @@ Hooks.once("init", async () => { Hooks.once("socketlib.ready", registerSocket); Hooks.on("canvasReady", module._canvasReady); + Hooks.on("preCreateChatMessage", module._preChatMessage); Hooks.on("preCreateToken", module._preCreatePile); Hooks.on("createToken", module._createPile); Hooks.on("deleteToken", module._deletePile); @@ -265,5 +266,21 @@ const module = { return true; } }); + }, + + _preChatMessage(chatMessage){ + + const content = chatMessage.data.content; + + if(!content.startsWith("!itempiles")) return; + + const args = content.split(" ").slice(1); + + if(args[0] === "trade"){ + TradingHandler.prompt(); + } + + return false; + } } \ No newline at end of file From bbdf12a69fa847086d87da53be74d4248516d9e9 Mon Sep 17 00:00:00 2001 From: Haxxer Date: Wed, 2 Feb 2022 01:52:48 +0000 Subject: [PATCH 05/23] First functional prompt and request --- languages/en.json | 30 ++- scripts/api.js | 4 +- scripts/chathandler.js | 19 ++ ...rencyDialog.js => drop-currency-dialog.js} | 0 ...{dropItemDialog.js => drop-item-dialog.js} | 0 ...{itemPileConfig.js => item-pile-config.js} | 4 +- ...itor.js => item-pile-currencies-editor.js} | 0 ...sEditor.js => item-pile-filters-editor.js} | 0 ...ileInventory.js => item-pile-inventory.js} | 4 +- .../formapplications/trade-request-dialog.js | 199 ++++++++++++++++++ .../{tradingApp.js => trading-app.js} | 108 +--------- scripts/libwrapper.js | 2 +- scripts/module.js | 24 +-- scripts/settings.js | 4 +- scripts/socket.js | 6 +- scripts/trade-api.js | 76 +++++++ styles/module.css | 8 +- styles/module.css.map | 2 +- styles/module.scss | 9 +- templates/trade-request-dialog.html | 139 ++++++++++++ 20 files changed, 486 insertions(+), 152 deletions(-) rename scripts/formapplications/{dropCurrencyDialog.js => drop-currency-dialog.js} (100%) rename scripts/formapplications/{dropItemDialog.js => drop-item-dialog.js} (100%) rename scripts/formapplications/{itemPileConfig.js => item-pile-config.js} (98%) rename scripts/formapplications/{itemPileCurrenciesEditor.js => item-pile-currencies-editor.js} (100%) rename scripts/formapplications/{itemPileFiltersEditor.js => item-pile-filters-editor.js} (100%) rename scripts/formapplications/{itemPileInventory.js => item-pile-inventory.js} (99%) create mode 100644 scripts/formapplications/trade-request-dialog.js rename scripts/formapplications/{tradingApp.js => trading-app.js} (62%) create mode 100644 scripts/trade-api.js create mode 100644 templates/trade-request-dialog.html diff --git a/languages/en.json b/languages/en.json index 445a450f..34525881 100644 --- a/languages/en.json +++ b/languages/en.json @@ -30,10 +30,34 @@ }, "Trade": { + "Title": "Item Piles: Trading", + + "Accept": "Accept", + "Decline": "Decline", + "Mute": "Mute", + "AutoDecline": "You did not respond to the trade request quickly enough, so it was auto-declined.", + + "Request": { + "Title": "Item Piles: Send Trade Request", + "User": "Select which user you want to trade with:", + "PickActor": "Select which actor to represent you in the trade:", + "PickedActor": "The actor that will represent you in the trade is:", + "PickToken": "Pick selected token", + "DropActor": "Drag and drop actor here", + "Label": "Send trade request" + }, + "NoActiveUsers": { + "Title": "Item Piles: No Active Users", + "Content": "No players are active, so you have no one to trade with." + }, "Prompt": { - "Title": "Trade request", - "Content": "{trader_player_name} (as {trader_actor_name}) wants to trade with you ({actor_name}).

Do you accept?" - } + "Title": "Item Piles: Trade Request", + "Content": "

{trader_actor_name} (user {trader_user_name}) wants to trade with you.

Do you accept?

" + }, + + "UserCharacterWarning": "You picked the actor \"{actor_name}\" which is the assigned character of the player \"{player_name}\".

Are you sure you want to do this?", + "UserActiveCharacterWarning": "You picked the actor \"{actor_name}\" which is the assigned character of the player \"{player_name}\", who is active.

Are you sure you want to do this?", + "ActorOwnerWarning": "You do not own this actor, so you cannot trade with it." }, "Errors": { diff --git a/scripts/api.js b/scripts/api.js index c69a93eb..c350b6c8 100644 --- a/scripts/api.js +++ b/scripts/api.js @@ -1,8 +1,8 @@ import * as lib from "./lib/lib.js"; import CONSTANTS from "./constants.js"; import { itemPileSocket, SOCKET_HANDLERS } from "./socket.js"; -import { ItemPileInventory } from "./formapplications/itemPileInventory.js"; -import DropItemDialog from "./formapplications/dropItemDialog.js"; +import { ItemPileInventory } from "./formapplications/item-pile-inventory.js"; +import DropItemDialog from "./formapplications/drop-item-dialog.js"; import HOOKS from "./hooks.js"; import { hotkeyState } from "./hotkeys.js"; diff --git a/scripts/chathandler.js b/scripts/chathandler.js index 0325d008..60422d87 100644 --- a/scripts/chathandler.js +++ b/scripts/chathandler.js @@ -2,9 +2,28 @@ import API from "./api.js"; import CONSTANTS from "./constants.js"; import * as lib from "./lib/lib.js"; import { itemPileSocket, SOCKET_HANDLERS } from "./socket.js"; +import { TradingAPI } from "./trade-api.js"; const chatHandler = { + _preCreateChatMessage(chatMessage){ + + const content = chatMessage.data.content; + + if(!(content.startsWith("!itempiles") || content.startsWith("!ip"))) return; + + const args = content.split(" ").slice(1); + + if(args[0] === "trade"){ + setTimeout(() => { + TradingAPI.prompt(); + }); + } + + return false; + + }, + /** * Outputs to chat based on transferring an item from or to an item pile * diff --git a/scripts/formapplications/dropCurrencyDialog.js b/scripts/formapplications/drop-currency-dialog.js similarity index 100% rename from scripts/formapplications/dropCurrencyDialog.js rename to scripts/formapplications/drop-currency-dialog.js diff --git a/scripts/formapplications/dropItemDialog.js b/scripts/formapplications/drop-item-dialog.js similarity index 100% rename from scripts/formapplications/dropItemDialog.js rename to scripts/formapplications/drop-item-dialog.js diff --git a/scripts/formapplications/itemPileConfig.js b/scripts/formapplications/item-pile-config.js similarity index 98% rename from scripts/formapplications/itemPileConfig.js rename to scripts/formapplications/item-pile-config.js index 93128074..64d128c7 100644 --- a/scripts/formapplications/itemPileConfig.js +++ b/scripts/formapplications/item-pile-config.js @@ -1,8 +1,8 @@ import CONSTANTS from "../constants.js"; import API from "../api.js"; -import { ItemPileCurrenciesEditor } from "./itemPileCurrenciesEditor.js"; +import { ItemPileCurrenciesEditor } from "./item-pile-currencies-editor.js"; import * as lib from "../lib/lib.js"; -import { ItemPileFiltersEditor } from "./itemPileFiltersEditor.js"; +import { ItemPileFiltersEditor } from "./item-pile-filters-editor.js"; export class ItemPileConfig extends FormApplication { diff --git a/scripts/formapplications/itemPileCurrenciesEditor.js b/scripts/formapplications/item-pile-currencies-editor.js similarity index 100% rename from scripts/formapplications/itemPileCurrenciesEditor.js rename to scripts/formapplications/item-pile-currencies-editor.js diff --git a/scripts/formapplications/itemPileFiltersEditor.js b/scripts/formapplications/item-pile-filters-editor.js similarity index 100% rename from scripts/formapplications/itemPileFiltersEditor.js rename to scripts/formapplications/item-pile-filters-editor.js diff --git a/scripts/formapplications/itemPileInventory.js b/scripts/formapplications/item-pile-inventory.js similarity index 99% rename from scripts/formapplications/itemPileInventory.js rename to scripts/formapplications/item-pile-inventory.js index aba00cdb..5370be93 100644 --- a/scripts/formapplications/itemPileInventory.js +++ b/scripts/formapplications/item-pile-inventory.js @@ -3,8 +3,8 @@ import API from "../api.js"; import * as lib from "../lib/lib.js"; import { isPileInventoryOpenForOthers } from "../socket.js"; import HOOKS from "../hooks.js"; -import { ItemPileConfig } from "./itemPileConfig.js"; -import DropCurrencyDialog from "./dropCurrencyDialog.js"; +import { ItemPileConfig } from "./item-pile-config.js"; +import DropCurrencyDialog from "./drop-currency-dialog.js"; export class ItemPileInventory extends FormApplication { diff --git a/scripts/formapplications/trade-request-dialog.js b/scripts/formapplications/trade-request-dialog.js new file mode 100644 index 00000000..e206486d --- /dev/null +++ b/scripts/formapplications/trade-request-dialog.js @@ -0,0 +1,199 @@ +import CONSTANTS from "../constants.js"; +import * as lib from "../lib/lib.js"; + +export class TradeRequestDialog extends FormApplication { + + constructor(resolve, users = false) { + super(); + this.resolve = resolve; + this.users = users; + this.user = users?.[0] ?? ""; + this.actor = false; + this.isGM = game.user.isGM; + + this.actors = game.actors.filter(actor => actor.isOwner); + this.preselectedActor = false; + if(this.actors.length === 1){ + this.actor = this.actors[0]; + this.preselectedActor = true; + }else if(game.user.character){ + this.actor = game.user.character; + }else if(game.user.isGM && canvas.tokens.controlled.length){ + this.actor = canvas.tokens.controlled[0].actor; + } + } + + static show(users){ + return new Promise(resolve => { + new TradeRequestDialog(resolve, users).render(true); + }) + } + + /** @inheritdoc */ + static get defaultOptions() { + return foundry.utils.mergeObject(super.defaultOptions, { + title: game.i18n.localize("ITEM-PILES.Trade.Title"), + classes: ["dialog"], + template: `${CONSTANTS.PATH}templates/trade-request-dialog.html`, + width: 400, + height: "auto", + dragDrop: [{ dragSelector: null, dropSelector: ".item-piles-actor-container" }], + }); + } + + activateListeners(html) { + super.activateListeners(html); + const self = this; + + if(!this.preselectedActor) { + html.find(".item-piles-actor-container").on("dragenter", function () { + $(this).addClass("item-piles-box-highlight"); + }) + + html.find(".item-piles-actor-container").on("dragleave", function () { + $(this).removeClass("item-piles-box-highlight"); + }) + + html.find(".item-piles-pick-selected-token").click(() => { + if(canvas.tokens.controlled.length === 0) return; + this.setActor(canvas.tokens.controlled[0].actor); + }); + } + + html.find(".item-piles-actor-select").change(function(){ + console.log($(this).val()) + console.log(game.actors.get($(this).val())); + self.setActor(game.actors.get($(this).val())); + }); + } + + async _onDrop(event) { + + super._onDrop(event); + + let data; + try { + data = JSON.parse(event.dataTransfer.getData('text/plain')); + } catch (err) { + return false; + } + + if(data.type !== "Actor") return; + + this.setActor(game.actors.get(data.id)); + + } + + setActor(actor){ + if(!actor.isOwner){ + return lib.custom_warning(game.i18n.localize("ITEM-PILES.Trade.ActorOwnerWarning"), true); + } + this.actor = actor; + this.render(true); + } + + async getData(options) { + const data = await super.getData(options); + data.users = this.users; + data.actor = this.actor; + data.actors = this.actors; + data.preselectedActor = this.preselectedActor; + data.multipleActors = this.actors.length > 1 && !game.user.isGM; + data.buttons = [{ + value: "accept", + icon: "fas fa-check", + text: game.i18n.localize("ITEM-PILES.Trade.Request.Label"), + disabled: !data.actor + }] + return data; + } + + async _updateObject(event, formData) { + return this.resolve({ + userId: formData.user, + actorUuid: formData?.actor || this.actor.uuid + }); + } + + async close(...args) { + super.close(...args); + } + +} + +export class TradePromptDialog extends TradeRequestDialog { + + constructor(resolve, traderUser, traderActor) { + super(resolve); + this.traderUser = traderUser; + this.traderActor = traderActor; + this.progressbarTimeout = false + this.timeout = false; + } + + static show(traderUser, traderActor){ + return new Promise(resolve => { + new TradePromptDialog(resolve, traderUser, traderActor).render(true); + }) + } + + activateListeners(html) { + super.activateListeners(html); + + const progressBarContainer = html.find(".item-piles-progress"); + const progressBar = html.find(".progress-bar"); + progressBarContainer.hide(); + this.progressbarTimeout = setTimeout(() => { + progressBarContainer.fadeIn(1000) + progressBar.css("transition", 'width 20s linear') + progressBar.css("width", "100%") + this.setPosition(); + }, 14000); + + this.timeout = setTimeout(() => { + lib.custom_warning(game.i18n.localize("ITEM-PILES.Trade.AutoDecline"), true) + html.find('button[name="decline"]').click(); + }, 35000) + + } + + async getData(options) { + let data = await super.getData(options); + data.isPrompt = true; + data.traderUser = this.traderUser; + data.traderActor = this.traderActor; + data.buttons = [{ + value: "accept", + icon: "fas fa-check", + text: game.i18n.localize("ITEM-PILES.Trade.Accept") + },{ + value: "decline", + icon: "fas fa-times", + text: game.i18n.localize("ITEM-PILES.Trade.Decline") + },{ + value: "mute", + icon: "fas fa-comment-slash", + text: game.i18n.localize("ITEM-PILES.Trade.Mute") + }]; + return data; + } + + async _updateObject(event, formData) { + + if(this.progressbarTimeout) clearTimeout(this.progressbarTimeout); + if(this.timeout) clearTimeout(this.timeout); + + if(event.submitter.value === "accept"){ + return this.resolve({ + actorUuid: formData?.actor || this.actor.uuid + }) + } + + if(event.submitter.value === "mute"){ + return this.resolve(false); + } + + return this.resolve(false); + } + +} \ No newline at end of file diff --git a/scripts/formapplications/tradingApp.js b/scripts/formapplications/trading-app.js similarity index 62% rename from scripts/formapplications/tradingApp.js rename to scripts/formapplications/trading-app.js index 5be64698..bdaecbd5 100644 --- a/scripts/formapplications/tradingApp.js +++ b/scripts/formapplications/trading-app.js @@ -1,9 +1,7 @@ import CONSTANTS from "../constants.js"; -import API from "../api.js"; import * as lib from "../lib/lib.js"; -import { itemPileSocket, SOCKET_HANDLERS } from "../socket.js"; import { hotkeyState } from "../hotkeys.js"; -import DropCurrencyDialog from "./dropCurrencyDialog.js"; +import DropCurrencyDialog from "./drop-currency-dialog.js"; export class TradingApp extends FormApplication { @@ -230,108 +228,4 @@ export class TradingApp extends FormApplication { } -} - - -export class TradingHandler { - - static async prompt(){ - - let content = `

Pick which player you want to trade with, and which actor represents you in the trade:

`; - - const users = game.users.filter(user => user.active && user !== game.user).map(user => { - return ``; - }); - - const actors = game.actors.filter(actor => actor.isOwner && actor.data.token.actorLink).map(actor => { - return ``; - }); - - content += `
` - content += `
` - - const [userId, actorUuid] = await Dialog.prompt({ - title: "Trading Request: Pick an actor", - content: content, - callback: (html) => { - const userId = html.find('select[name="user"]').val() - const actorUuid = html.find('select[name="actor"]').val() - return [userId, actorUuid] - } - }) - - if(!actorUuid) return false; - - const response = await itemPileSocket.executeAsUser(SOCKET_HANDLERS.TRADE_PROMPT, userId, game.user.id, actorUuid); - - if(!response) return; - - } - - static async _respondPrompt(userId, traderUuid){ - - const tradingUser = game.users.get(userId); - const trader = await fromUuid(traderUuid); - - return await new Promise(resolve => { - let resolved = false; - const dialog = new Dialog({ - title: game.i18n.localize("ITEM-PILES.Trade.Prompt.Title"), - content: lib.dialogLayout({ - icon: "fas fa-handshake", - title: "Trade Request", - message: game.i18n.format("ITEM-PILES.Trade.Prompt.Content", { - trader_player_name: tradingUser.name, - trader_actor_name: trader.name, - actor_name: game.user.character.name - }), - extraHtml: ` -
- -
` - }), - buttons: { - confirm: { - icon: '', - label: game.i18n.localize("Yes"), - callback: () => { - resolved = true; - resolve(true) - } - }, - cancel: { - icon: '', - label: game.i18n.localize("No"), - callback: () => { - resolved = true; - resolve(false); - } - } - }, - default: "cancel", - render: (html) => { - const progressBarContainer = html.find(".item-piles-progress"); - const progressBar = html.find(".progress-bar"); - progressBarContainer.css("opacity", "0"); - setTimeout(() => { - progressBarContainer.fadeTo(1, 1000) - progressBar.css("transition", 'width 20s linear') - progressBar.css("width", "100%") - }, 14000); - } - }) - - setTimeout(() => { - if(resolved) return; - lib.custom_warning("You did not respond to the trade request quickly enough, and thus auto-declined it.") - dialog.close(); - resolve(false); - }, 35000) - - dialog.render(true); - }) - - } - - } \ No newline at end of file diff --git a/scripts/libwrapper.js b/scripts/libwrapper.js index 5743506d..9b747c51 100644 --- a/scripts/libwrapper.js +++ b/scripts/libwrapper.js @@ -1,7 +1,7 @@ import API from "./api.js"; import CONSTANTS from "./constants.js"; import { hotkeyActionState } from "./hotkeys.js"; -import { ItemPileInventory } from "./formapplications/itemPileInventory.js"; +import { ItemPileInventory } from "./formapplications/item-pile-inventory.js"; export function registerLibwrappers() { diff --git a/scripts/module.js b/scripts/module.js index 8465b12d..beb6ace8 100644 --- a/scripts/module.js +++ b/scripts/module.js @@ -6,13 +6,13 @@ import chatHandler from "./chathandler.js"; import API from "./api.js"; import * as lib from "./lib/lib.js"; -import { ItemPileConfig } from "./formapplications/itemPileConfig.js"; +import { ItemPileConfig } from "./formapplications/item-pile-config.js"; import { registerSettings, checkSystem, migrateSettings, registerHandlebarHelpers } from "./settings.js"; import { registerSocket } from "./socket.js"; import { registerLibwrappers } from "./libwrapper.js"; import { registerHotkeysPre, registerHotkeysPost } from "./hotkeys.js"; import { getActorCurrencies, getActorItems } from "./lib/lib.js"; -import { TradingHandler } from "./formapplications/tradingApp.js"; +import { TradingAPI } from "./trade-api.js"; Hooks.once("init", async () => { @@ -22,7 +22,6 @@ Hooks.once("init", async () => { Hooks.once("socketlib.ready", registerSocket); Hooks.on("canvasReady", module._canvasReady); - Hooks.on("preCreateChatMessage", module._preChatMessage); Hooks.on("preCreateToken", module._preCreatePile); Hooks.on("createToken", module._createPile); Hooks.on("deleteToken", module._deletePile); @@ -35,6 +34,7 @@ Hooks.once("init", async () => { Hooks.on("getActorDirectoryEntryContext", module._handleActorContextMenu); Hooks.on("renderTokenHUD", module._renderPileHUD); + Hooks.on("preCreateChatMessage", chatHandler._preCreateChatMessage.bind(chatHandler)); Hooks.on(HOOKS.ITEM.TRANSFER, chatHandler._outputTransferItem.bind(chatHandler)); Hooks.on(HOOKS.ATTRIBUTE.TRANSFER, chatHandler._outputTransferCurrency.bind(chatHandler)); Hooks.on(HOOKS.TRANSFER_EVERYTHING, chatHandler._outputTransferEverything.bind(chatHandler)); @@ -56,7 +56,7 @@ Hooks.once("init", async () => { window.ItemPiles = { API, - TradingHandler + TradingHandler: TradingAPI } }); @@ -266,21 +266,5 @@ const module = { return true; } }); - }, - - _preChatMessage(chatMessage){ - - const content = chatMessage.data.content; - - if(!content.startsWith("!itempiles")) return; - - const args = content.split(" ").slice(1); - - if(args[0] === "trade"){ - TradingHandler.prompt(); - } - - return false; - } } \ No newline at end of file diff --git a/scripts/settings.js b/scripts/settings.js index c56e0e76..5443eff8 100644 --- a/scripts/settings.js +++ b/scripts/settings.js @@ -1,8 +1,8 @@ import CONSTANTS from "./constants.js"; -import { ItemPileCurrenciesEditor } from "./formapplications/itemPileCurrenciesEditor.js"; +import { ItemPileCurrenciesEditor } from "./formapplications/item-pile-currencies-editor.js"; import { SYSTEMS } from "./systems.js"; import * as lib from "./lib/lib.js"; -import { ItemPileFiltersEditor } from "./formapplications/itemPileFiltersEditor.js"; +import { ItemPileFiltersEditor } from "./formapplications/item-pile-filters-editor.js"; import flagManager from "./flagManager.js"; function defaultSettings(apply = false) { diff --git a/scripts/socket.js b/scripts/socket.js index 4d2f7191..501ea3c3 100644 --- a/scripts/socket.js +++ b/scripts/socket.js @@ -1,9 +1,9 @@ import * as lib from "./lib/lib.js"; import CONSTANTS from "./constants.js"; import API from "./api.js"; -import { ItemPileInventory } from "./formapplications/itemPileInventory.js"; +import { ItemPileInventory } from "./formapplications/item-pile-inventory.js"; import chatHandler from "./chathandler.js"; -import { TradingHandler } from "./formapplications/tradingApp.js"; +import { TradingAPI } from "./trade-api.js"; export const SOCKET_HANDLERS = { /** @@ -114,7 +114,7 @@ export function registerSocket() { /** * Trading sockets */ - itemPileSocket.register(SOCKET_HANDLERS.TRADE_PROMPT, (...args) => TradingHandler._respondPrompt(...args)) + itemPileSocket.register(SOCKET_HANDLERS.TRADE_PROMPT, (...args) => TradingAPI._respondPrompt(...args)) } async function callHook(inHookName, ...args) { diff --git a/scripts/trade-api.js b/scripts/trade-api.js new file mode 100644 index 00000000..bc7ef8e2 --- /dev/null +++ b/scripts/trade-api.js @@ -0,0 +1,76 @@ +import { itemPileSocket, SOCKET_HANDLERS } from "./socket.js"; +import * as lib from "./lib/lib.js"; +import { TradePromptDialog, TradeRequestDialog } from "./formapplications/trade-request-dialog.js"; + +export class TradingAPI { + + static async prompt(){ + + const users = game.users.filter(user => user.active && user !== game.user); + + if(!users.length){ + return new Dialog({ + title: game.i18n.localize("ITEM-PILES.Trade.Title"), + content: lib.dialogLayout({ + title: game.i18n.localize("ITEM-PILES.Trade.NoActiveUsers.Title"), + message: game.i18n.localize("ITEM-PILES.Trade.NoActiveUsers.Content"), + icon: "fas fa-heart-broken" + }), + buttons: { + ok: { + icon: '', + label: game.i18n.localize("Okay") + } + } + }).render(true); + } + + const result = await TradeRequestDialog.show(users); + if(!result) return; + + const { userId, actorUuid } = result; + + const actor = await fromUuid(actorUuid); + const actorOwner = game.users.find(user => user.character === actor && user !== game.user); + if(actorOwner){ + + const doContinue = await Dialog.confirm({ + title: game.i18n.localize("ITEM-PILES.Trade.Title"), + content: lib.dialogLayout({ + message: actorOwner.active + ? game.i18n.format("ITEM-PILES.Trade.UserCharacterWarning", { actor_name: actor.name, player_name: actorOwner.name }) + : game.i18n.format("ITEM-PILES.Trade.UserActiveCharacterWarning", { actor_name: actor.name, player_name: actorOwner.name }) + }), + defaultYes: false + }); + if(!doContinue){ + return; + } + } + + if(!actorUuid) return false; + + const tradeId = randomID(); + + const response = await itemPileSocket.executeAsUser(SOCKET_HANDLERS.TRADE_PROMPT, userId, game.user.id, actorUuid, tradeId); + + if(!response || !response.id.includes(tradeId)) return false; + + console.log(response.id); + + } + + static async _respondPrompt(tradingUserId, tradingActorUuid, inTradeId){ + + const tradeId = inTradeId + randomID(); + + const tradingUser = game.users.get(tradingUserId); + const tradingActor = await fromUuid(tradingActorUuid); + + TradePromptDialog.show(tradingUser, tradingActor); + + return false; + + } + +} \ No newline at end of file diff --git a/styles/module.css b/styles/module.css index f63b9b66..9204c6c4 100644 --- a/styles/module.css +++ b/styles/module.css @@ -1,16 +1,16 @@ /* ----------------------- Generic ----------------------- */ .item-piles-progress { - width: calc(100% - 10px); - height: 6px; + width: calc(100% - 4px); background: #e1e4e8; border-radius: 3px; overflow: hidden; - margin: 0 5px; + margin: 0.25rem 2px 0 2px; + height: 6px; } .item-piles-progress .progress-bar { display: block; height: 100%; - background-color: rgba(0, 0, 0, 0.4); + background-color: rgba(0, 0, 0, 0.65); transition: width 30s linear; } .item-piles-dialog { diff --git a/styles/module.css.map b/styles/module.css.map index 0a7f6b6a..2bf6bb00 100644 --- a/styles/module.css.map +++ b/styles/module.css.map @@ -1 +1 @@ -{"version":3,"sourceRoot":"","sources":["module.scss"],"names":[],"mappings":"AAAA;AAIE;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EAEA;;AAKJ;EACE;EACA;EACA;;AAGF;EACE;;AAGF;EACE;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE;;AAIJ;EACE;;AAGF;EACE;EACA;;AAGF;EACE;;AAIA;EACE;;AAEF;EACE;;AAIJ;EACE;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EAEA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAKN;EACE;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;;;AAKJ;EACE;EACA;EACA;EACA;;;AAGF;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;;;AAIJ;AAGA;EACE;EACA;;AAEA;EACE;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAIJ;EACE;EACA;;AAIJ;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;;AAQN;AAEA;EACE;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAKA;EACE;EACA;EACA;;AAGF;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAIJ;EACE;;AAEA;EACE;EACA;EACA;EACA;;AAKN;EACE;EACA;EACA;;;AAON;AAEA;EACE;;AAEA;EACE;EACA;;AAIA;EACE;EACA;;AAGF;EACE;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;;;AAKN;AAKE;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAKA;EACE;EACA;EACA;EAEA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;;AASR;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EAEE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAIA;EACE","file":"module.css"} \ No newline at end of file +{"version":3,"sourceRoot":"","sources":["module.scss"],"names":[],"mappings":"AAAA;AAIE;EACE;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAKJ;EACE;EACA;EACA;;AAGF;EACE;;AAGF;EACE;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE;;AAIJ;EACE;;AAGF;EACE;EACA;;AAGF;EACE;;AAIA;EACE;;AAEF;EACE;;AAIJ;EACE;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EAEA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAKN;EACE;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;;;AAKJ;EACE;EACA;EACA;EACA;;;AAGF;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;;;AAIJ;AAGA;EACE;EACA;;AAEA;EACE;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;;AAIJ;EACE;EACA;;AAIJ;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;;;AAQN;AAEA;EACE;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAKA;EACE;EACA;EACA;;AAGF;EACE;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAIJ;EACE;;AAEA;EACE;EACA;EACA;EACA;;AAKN;EACE;EACA;EACA;;;AAON;AAEA;EACE;;AAEA;EACE;EACA;;AAIA;EACE;EACA;;AAGF;EACE;;AAIJ;EACE;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAEA;EACE;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;;;AAKN;AAKE;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;EACA;;AAGF;EACE;EACA;;AAGF;EACE;EACA;;AAKA;EACE;EACA;EACA;EAEA;EACA;EACA;;AAEA;EACE;EACA;EACA;EACA;;AAEA;EACE;;AASR;EACE;EACA;;AAGF;EACE;EACA;;AAGF;EAEE;EACA;EACA;EACA;;AAEA;EACE;EACA;EACA;;AAGF;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAIA;EACE","file":"module.css"} \ No newline at end of file diff --git a/styles/module.scss b/styles/module.scss index acb9184e..f806dfeb 100644 --- a/styles/module.scss +++ b/styles/module.scss @@ -3,18 +3,17 @@ .item-piles { &-progress { - width: calc(100% - 10px); - height: 6px; + width: calc(100% - 4px); background: #e1e4e8; border-radius: 3px; overflow: hidden; - margin: 0 5px; + margin: 0.25rem 2px 0 2px; + height: 6px; .progress-bar { display: block; height: 100%; - background-color: rgba(0,0,0, 0.4); - + background-color: rgba(0,0,0, 0.65); transition: width 30s linear; } } diff --git a/templates/trade-request-dialog.html b/templates/trade-request-dialog.html new file mode 100644 index 00000000..ddb5b4bd --- /dev/null +++ b/templates/trade-request-dialog.html @@ -0,0 +1,139 @@ +
+ + + +

+ +

+ {{#if isPrompt}} + {{localize "ITEM-PILES.Trade.Prompt.Title"}} + {{else}} + {{localize "ITEM-PILES.Trade.Request.Title"}} + {{/if}} +

+ + {{#if isPrompt}} + +
{{{localize "ITEM-PILES.Trade.Prompt.Content" trader_user_name=traderUser.name + trader_actor_name=traderActor.name}}}
+ + {{else}} + +

{{localize "ITEM-PILES.Trade.Request.User"}}

+ +
+
+ + {{#select user.id}} + + {{/select}} +
+
+ + {{/if}} + + {{#if actor}} +

{{localize "ITEM-PILES.Trade.Request.PickedActor"}}

+ {{else}} +

{{localize "ITEM-PILES.Trade.Request.PickActor"}}

+ {{/if}} + +
+
+ {{#if actor}} +
+ {{#if actor.data.img}} + + {{/if}} + {{actor.name}} +
+ {{else}} +

{{ localize "ITEM-PILES.Trade.Request.DropActor" }}

+ {{/if}} +
+ {{#unless preselectedActor}} +
+ {{#unless isGM}} + {{#if multipleActors}} + {{#select actor.uuid}} + + {{/select}} + {{/if}} + {{/unless}} + +
+ {{/unless}} +
+ +
+ {{#each buttons as |button id|}} + + {{/each}} +
+ + {{#if isPrompt}} +
+ +
+ {{/if}} + +
\ No newline at end of file From 4bdcc8c5a7aebf7b95ae054e72c04de0e06d07ae Mon Sep 17 00:00:00 2001 From: Haxxer Date: Wed, 2 Feb 2022 08:38:08 +0000 Subject: [PATCH 06/23] renames --- .../{trade-request-dialog.js => trade-dialogs.js} | 8 ++++---- scripts/trade-api.js | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) rename scripts/formapplications/{trade-request-dialog.js => trade-dialogs.js} (92%) diff --git a/scripts/formapplications/trade-request-dialog.js b/scripts/formapplications/trade-dialogs.js similarity index 92% rename from scripts/formapplications/trade-request-dialog.js rename to scripts/formapplications/trade-dialogs.js index e206486d..4b46a1df 100644 --- a/scripts/formapplications/trade-request-dialog.js +++ b/scripts/formapplications/trade-dialogs.js @@ -1,7 +1,7 @@ import CONSTANTS from "../constants.js"; import * as lib from "../lib/lib.js"; -export class TradeRequestDialog extends FormApplication { +export class TradePromptDialog extends FormApplication { constructor(resolve, users = false) { super(); @@ -25,7 +25,7 @@ export class TradeRequestDialog extends FormApplication { static show(users){ return new Promise(resolve => { - new TradeRequestDialog(resolve, users).render(true); + new TradePromptDialog(resolve, users).render(true); }) } @@ -121,7 +121,7 @@ export class TradeRequestDialog extends FormApplication { } -export class TradePromptDialog extends TradeRequestDialog { +export class TradeRequestDialog extends TradePromptDialog { constructor(resolve, traderUser, traderActor) { super(resolve); @@ -133,7 +133,7 @@ export class TradePromptDialog extends TradeRequestDialog { static show(traderUser, traderActor){ return new Promise(resolve => { - new TradePromptDialog(resolve, traderUser, traderActor).render(true); + new TradeRequestDialog(resolve, traderUser, traderActor).render(true); }) } diff --git a/scripts/trade-api.js b/scripts/trade-api.js index bc7ef8e2..b212afe9 100644 --- a/scripts/trade-api.js +++ b/scripts/trade-api.js @@ -1,6 +1,6 @@ import { itemPileSocket, SOCKET_HANDLERS } from "./socket.js"; import * as lib from "./lib/lib.js"; -import { TradePromptDialog, TradeRequestDialog } from "./formapplications/trade-request-dialog.js"; +import { TradeRequestDialog, TradePromptDialog } from "./formapplications/trade-dialogs.js"; export class TradingAPI { @@ -25,7 +25,7 @@ export class TradingAPI { }).render(true); } - const result = await TradeRequestDialog.show(users); + const result = await TradePromptDialog.show(users); if(!result) return; const { userId, actorUuid } = result; @@ -67,7 +67,7 @@ export class TradingAPI { const tradingUser = game.users.get(tradingUserId); const tradingActor = await fromUuid(tradingActorUuid); - TradePromptDialog.show(tradingUser, tradingActor); + TradeRequestDialog.show(tradingUser, tradingActor); return false; From 4ac96b5ec0b16544ee6f7a01e99a2131bc64b5b3 Mon Sep 17 00:00:00 2001 From: Haxxer Date: Wed, 2 Feb 2022 09:55:10 +0000 Subject: [PATCH 07/23] wording --- languages/en.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/languages/en.json b/languages/en.json index 34525881..b39f84ef 100644 --- a/languages/en.json +++ b/languages/en.json @@ -40,8 +40,8 @@ "Request": { "Title": "Item Piles: Send Trade Request", "User": "Select which user you want to trade with:", - "PickActor": "Select which actor to represent you in the trade:", - "PickedActor": "The actor that will represent you in the trade is:", + "PickActor": "Select which actor you will represent in the trade:", + "PickedActor": "The actor that you will represent in the trade:", "PickToken": "Pick selected token", "DropActor": "Drag and drop actor here", "Label": "Send trade request" From 8cba8793aa029b2078521572a67e99d4d0c8022f Mon Sep 17 00:00:00 2001 From: Haxxer Date: Wed, 2 Feb 2022 17:10:36 +0000 Subject: [PATCH 08/23] Further trade work --- README.md | 2 +- languages/en.json | 15 ++- scripts/chathandler.js | 4 +- scripts/formapplications/trade-dialogs.js | 93 ++++++++++++------- scripts/module.js | 15 ++- scripts/socket.js | 6 +- scripts/trade-api.js | 74 ++++++++++++--- ...-request-dialog.html => trade-dialog.html} | 43 +++++---- 8 files changed, 172 insertions(+), 80 deletions(-) rename templates/{trade-request-dialog.html => trade-dialog.html} (77%) diff --git a/README.md b/README.md index a0e68366..edc94169 100644 --- a/README.md +++ b/README.md @@ -104,7 +104,7 @@ Any player or GM can drag & drop items - if you drag & drop an item from an acto ![Dropping items into a chest](./docs/images/wiki-drop-into.jpg) -Holding ALT before dragging & dropping an item will make you automatically drop 1 of the items into a new pile without a prompt. **You can also drag and drop items onto an existing pile**, where holding ALT is also supported. +Holding ALT before dragging & dropping an item will make you automatically drop 1 of the items into a new pile without a promptUser. **You can also drag and drop items onto an existing pile**, where holding ALT is also supported. When players double-click on the item pile (or Left Control + double-click for GMs to inspect, or to inspect as someone else select two tokens, and repeat), they will get a custom UI where they can choose what they want to take from the item pile, or all of it (if they're loot goblins). diff --git a/languages/en.json b/languages/en.json index b39f84ef..128c6963 100644 --- a/languages/en.json +++ b/languages/en.json @@ -7,7 +7,7 @@ "ContextMenu": { "ShowToPlayers": "Item Piles: Show pile to players", - "TradeWith": "Item Piles: Initiate Trade" + "RequestTrade": "Item Piles: Request Trade" }, "Inspect": { @@ -46,13 +46,24 @@ "DropActor": "Drag and drop actor here", "Label": "Send trade request" }, + "NoActiveUsers": { "Title": "Item Piles: No Active Users", "Content": "No players are active, so you have no one to trade with." }, + "Prompt": { "Title": "Item Piles: Trade Request", - "Content": "

{trader_actor_name} (user {trader_user_name}) wants to trade with you.

Do you accept?

" + "Content": "

{trading_actor_name} (user {trading_user_name}) wants to trade with you.

Do you accept?

" + }, + + "OngoingRequest": { + "Content": "Wait for response from {user_name}...", + "Label": "Cancel trade request" + }, + + "CancelledRequest": { + "Content": "The trade request was cancelled by {user_name}." }, "UserCharacterWarning": "You picked the actor \"{actor_name}\" which is the assigned character of the player \"{player_name}\".

Are you sure you want to do this?", diff --git a/scripts/chathandler.js b/scripts/chathandler.js index 60422d87..ebcbddb6 100644 --- a/scripts/chathandler.js +++ b/scripts/chathandler.js @@ -2,7 +2,7 @@ import API from "./api.js"; import CONSTANTS from "./constants.js"; import * as lib from "./lib/lib.js"; import { itemPileSocket, SOCKET_HANDLERS } from "./socket.js"; -import { TradingAPI } from "./trade-api.js"; +import { TradeAPI } from "./trade-api.js"; const chatHandler = { @@ -16,7 +16,7 @@ const chatHandler = { if(args[0] === "trade"){ setTimeout(() => { - TradingAPI.prompt(); + TradeAPI.promptUser(); }); } diff --git a/scripts/formapplications/trade-dialogs.js b/scripts/formapplications/trade-dialogs.js index 4b46a1df..1019e0a9 100644 --- a/scripts/formapplications/trade-dialogs.js +++ b/scripts/formapplications/trade-dialogs.js @@ -3,29 +3,22 @@ import * as lib from "../lib/lib.js"; export class TradePromptDialog extends FormApplication { - constructor(resolve, users = false) { + constructor(resolve, { actors = false, actor = false, users = false, user = false }={}) { super(); this.resolve = resolve; - this.users = users; - this.user = users?.[0] ?? ""; - this.actor = false; + this.users = users || game.users.filter(user => user.active && user !== game.user); + this.user = user || users?.[0] || false; + this.actors = actors || game.actors.filter(actor => actor.isOwner); + this.actor = actor || game.user.character ||this.actors?.[0] || false; this.isGM = game.user.isGM; - this.actors = game.actors.filter(actor => actor.isOwner); - this.preselectedActor = false; - if(this.actors.length === 1){ - this.actor = this.actors[0]; - this.preselectedActor = true; - }else if(game.user.character){ - this.actor = game.user.character; - }else if(game.user.isGM && canvas.tokens.controlled.length){ - this.actor = canvas.tokens.controlled[0].actor; - } + this.preselectedActor = this.actors.length === 1 || actor; + } - static show(users){ + static show({ actors = false, actor = false, users = false, user = false }){ return new Promise(resolve => { - new TradePromptDialog(resolve, users).render(true); + new TradePromptDialog(resolve, { actors, actor, users, user }).render(true); }) } @@ -34,7 +27,7 @@ export class TradePromptDialog extends FormApplication { return foundry.utils.mergeObject(super.defaultOptions, { title: game.i18n.localize("ITEM-PILES.Trade.Title"), classes: ["dialog"], - template: `${CONSTANTS.PATH}templates/trade-request-dialog.html`, + template: `${CONSTANTS.PATH}templates/trade-dialog.html`, width: 400, height: "auto", dragDrop: [{ dragSelector: null, dropSelector: ".item-piles-actor-container" }], @@ -46,12 +39,20 @@ export class TradePromptDialog extends FormApplication { const self = this; if(!this.preselectedActor) { - html.find(".item-piles-actor-container").on("dragenter", function () { - $(this).addClass("item-piles-box-highlight"); + html.find(".item-piles-actor-container").on("dragenter", function (event) { + event = event.originalEvent || event; + let newElement = document.elementFromPoint(event.pageX, event.pageY); + if (!$(this).contains(newElement)) { + $(this).addClass("item-piles-box-highlight"); + } }) - html.find(".item-piles-actor-container").on("dragleave", function () { - $(this).removeClass("item-piles-box-highlight"); + html.find(".item-piles-actor-container").on("dragleave", function (event) { + event = event.originalEvent || event; + let newElement = document.elementFromPoint(event.pageX, event.pageY); + if (!$(this).contains(newElement)) { + $(this).removeClass("item-piles-box-highlight"); + } }) html.find(".item-piles-pick-selected-token").click(() => { @@ -60,10 +61,18 @@ export class TradePromptDialog extends FormApplication { }); } - html.find(".item-piles-actor-select").change(function(){ - console.log($(this).val()) - console.log(game.actors.get($(this).val())); - self.setActor(game.actors.get($(this).val())); + html.find('.item-piles-change-actor').click(function () { + $(this).hide(); + let select = $(this).parent().find('.item-piles-change-actor-select'); + select.insertAfter($(this)); + select.css('display', 'inline-block'); + }); + + html.find(".item-piles-change-actor-select").change(async function () { + $(this).css('display', 'none'); + html.find('.item-piles-change-actor').show(); + const actor = await fromUuid($(this).val()); + self.setActor(actor); }); } @@ -94,11 +103,13 @@ export class TradePromptDialog extends FormApplication { async getData(options) { const data = await super.getData(options); + data.user = this.user; data.users = this.users; data.actor = this.actor; data.actors = this.actors; data.preselectedActor = this.preselectedActor; data.multipleActors = this.actors.length > 1 && !game.user.isGM; + data.hasUnlinkedTokenOwnership = this.actors.filter(a => !a.data.token.actorLink).length > 0; data.buttons = [{ value: "accept", icon: "fas fa-check", @@ -117,26 +128,38 @@ export class TradePromptDialog extends FormApplication { async close(...args) { super.close(...args); + this.resolve(false); } } export class TradeRequestDialog extends TradePromptDialog { - constructor(resolve, traderUser, traderActor) { + constructor(resolve, { tradeId, tradingUser, tradingActor }={}) { super(resolve); - this.traderUser = traderUser; - this.traderActor = traderActor; + this.tradeId = tradeId; + this.tradingUser = tradingUser; + this.tradingActor = tradingActor; this.progressbarTimeout = false this.timeout = false; } - static show(traderUser, traderActor){ + static show({ tradeId, tradingUser, tradingActor }={}){ return new Promise(resolve => { - new TradeRequestDialog(resolve, traderUser, traderActor).render(true); + new TradeRequestDialog(resolve, { tradeId, tradingUser, tradingActor }).render(true); }) } + static cancel(tradeId){ + for(const app of Object.values(ui.windows)){ + if(app instanceof TradeRequestDialog && app.tradeId === tradeId){ + app.close(); + return app; + } + } + return false; + } + activateListeners(html) { super.activateListeners(html); @@ -160,8 +183,8 @@ export class TradeRequestDialog extends TradePromptDialog { async getData(options) { let data = await super.getData(options); data.isPrompt = true; - data.traderUser = this.traderUser; - data.traderActor = this.traderActor; + data.tradingUser = this.tradingUser; + data.tradingActor = this.tradingActor; data.buttons = [{ value: "accept", icon: "fas fa-check", @@ -196,4 +219,10 @@ export class TradeRequestDialog extends TradePromptDialog { return this.resolve(false); } + async close(...args) { + super.close(...args); + if(this.progressbarTimeout) clearTimeout(this.progressbarTimeout); + if(this.timeout) clearTimeout(this.timeout); + } + } \ No newline at end of file diff --git a/scripts/module.js b/scripts/module.js index beb6ace8..84c67307 100644 --- a/scripts/module.js +++ b/scripts/module.js @@ -12,7 +12,7 @@ import { registerSocket } from "./socket.js"; import { registerLibwrappers } from "./libwrapper.js"; import { registerHotkeysPre, registerHotkeysPost } from "./hotkeys.js"; import { getActorCurrencies, getActorItems } from "./lib/lib.js"; -import { TradingAPI } from "./trade-api.js"; +import { TradeAPI } from "./trade-api.js"; Hooks.once("init", async () => { @@ -56,7 +56,7 @@ Hooks.once("init", async () => { window.ItemPiles = { API, - TradingHandler: TradingAPI + TradingHandler: TradeAPI } }); @@ -257,13 +257,18 @@ const module = { return game.user.isGM && API.isValidItemPile(actor); } }, { - name: "ITEM-PILES.ContextMenu.TradeWith", + name: "ITEM-PILES.ContextMenu.RequestTrade", icon: ``, callback: (html) => { - return true; + const actorId = html[0].dataset.documentId; + const actor = game.actors.get(actorId); + const user = Array.from(game.users).find(u => u.character === actor && u.active); + return TradeAPI.promptUser(user); }, condition: (html) => { - return true; + const actorId = html[0].dataset.documentId; + const actor = game.actors.get(actorId); + return game.user?.character !== actor || Array.from(game.users).find(u => u.character === actor && u.active); } }); } diff --git a/scripts/socket.js b/scripts/socket.js index 501ea3c3..bd3ccf97 100644 --- a/scripts/socket.js +++ b/scripts/socket.js @@ -3,7 +3,7 @@ import CONSTANTS from "./constants.js"; import API from "./api.js"; import { ItemPileInventory } from "./formapplications/item-pile-inventory.js"; import chatHandler from "./chathandler.js"; -import { TradingAPI } from "./trade-api.js"; +import { TradeAPI } from "./trade-api.js"; export const SOCKET_HANDLERS = { /** @@ -57,6 +57,7 @@ export const SOCKET_HANDLERS = { */ TRADE_PROMPT: "tradePrompt", TRADE_ACCEPTED: "tradeAccepted", + TRADE_CANCELLED: "tradeCancelled", }; export let itemPileSocket; @@ -114,7 +115,8 @@ export function registerSocket() { /** * Trading sockets */ - itemPileSocket.register(SOCKET_HANDLERS.TRADE_PROMPT, (...args) => TradingAPI._respondPrompt(...args)) + itemPileSocket.register(SOCKET_HANDLERS.TRADE_PROMPT, (...args) => TradeAPI._respondPrompt(...args)) + itemPileSocket.register(SOCKET_HANDLERS.TRADE_CANCELLED, (...args) => TradeAPI._tradeCancelled(...args)) } async function callHook(inHookName, ...args) { diff --git a/scripts/trade-api.js b/scripts/trade-api.js index b212afe9..ebfb231f 100644 --- a/scripts/trade-api.js +++ b/scripts/trade-api.js @@ -2,9 +2,9 @@ import { itemPileSocket, SOCKET_HANDLERS } from "./socket.js"; import * as lib from "./lib/lib.js"; import { TradeRequestDialog, TradePromptDialog } from "./formapplications/trade-dialogs.js"; -export class TradingAPI { +export class TradeAPI { - static async prompt(){ + static async promptUser(user = false){ const users = game.users.filter(user => user.active && user !== game.user); @@ -25,15 +25,23 @@ export class TradingAPI { }).render(true); } - const result = await TradePromptDialog.show(users); - if(!result) return; - - const { userId, actorUuid } = result; + let userId; + let actorUuid; + + const actors = game.actors.filter(actor => actor.isOwner); + if(actors.length === 1 && user){ + userId = user.id; + actorUuid = actors[0].uuid; + }else { + const result = await TradePromptDialog.show({ actors, users, user }); + if (!result) return; + userId = result.userId; + actorUuid = result.actorUuid; + } const actor = await fromUuid(actorUuid); const actorOwner = game.users.find(user => user.character === actor && user !== game.user); if(actorOwner){ - const doContinue = await Dialog.confirm({ title: game.i18n.localize("ITEM-PILES.Trade.Title"), content: lib.dialogLayout({ @@ -52,24 +60,62 @@ export class TradingAPI { const tradeId = randomID(); - const response = await itemPileSocket.executeAsUser(SOCKET_HANDLERS.TRADE_PROMPT, userId, game.user.id, actorUuid, tradeId); + const cancel = Dialog.prompt({ + title: game.i18n.localize("ITEM-PILES.Trade.Title"), + content: `

${game.i18n.format("ITEM-PILES.Trade.OngoingRequest.Content", { user_name: game.users.get(userId).name })}

`, + label: game.i18n.localize("ITEM-PILES.Trade.OngoingRequest.Label"), + callback: () => { return true }, + options: { + top: 50, + width: 300 + } + }); + + let response = itemPileSocket.executeAsUser(SOCKET_HANDLERS.TRADE_PROMPT, userId, game.user.id, actorUuid, tradeId); + + cancel.then((result) => { + if(!result) return; + response = null; + itemPileSocket.executeAsUser(SOCKET_HANDLERS.TRADE_CANCELLED, userId, game.user.id, tradeId); + }) + + response.then(() => { - if(!response || !response.id.includes(tradeId)) return false; + if(!response || !response.includes(tradeId)) return false; - console.log(response.id); + itemPileSocket.executeAsUser(SOCKET_HANDLERS.TRADE_ACCEPTED, userId, game.user.id, tradeId); + + }) } - static async _respondPrompt(tradingUserId, tradingActorUuid, inTradeId){ + static async _respondPrompt(tradingUserId, tradingActorUuid, tradeId){ - const tradeId = inTradeId + randomID(); + const fullTradeId = tradeId + randomID(); const tradingUser = game.users.get(tradingUserId); const tradingActor = await fromUuid(tradingActorUuid); - TradeRequestDialog.show(tradingUser, tradingActor); + await TradeRequestDialog.show({ tradeId, tradingUser, tradingActor }); + + return fullTradeId; + + } + + static async _tradeCancelled(userId, tradeId){ + + Dialog.prompt({ + title: game.i18n.localize("ITEM-PILES.Trade.Title"), + content: lib.dialogLayout({ + message: game.i18n.format("ITEM-PILES.Trade.CancelledRequest.Content", { user_name: game.users.get(userId).name }) + }), + callback: () => {}, + options: { + width: 300 + } + }); - return false; + return TradeRequestDialog.cancel(tradeId); } diff --git a/templates/trade-request-dialog.html b/templates/trade-dialog.html similarity index 77% rename from templates/trade-request-dialog.html rename to templates/trade-dialog.html index ddb5b4bd..76f427f7 100644 --- a/templates/trade-request-dialog.html +++ b/templates/trade-dialog.html @@ -19,10 +19,6 @@ box-shadow: 0 0 20px inset var(--color-shadow-highlight, #ff6400); } - .item-piles-actor-container > * { - pointer-events: none; - } - .item-piles-actor-container img { border: 0; max-height:80px; @@ -39,9 +35,8 @@ text-align: center; } - .item-piles-actor-select{ - height: 30px; - margin: 2px 5px 2px 0; + .item-piles-change-actor-select{ + height: 23px; } @@ -58,8 +53,8 @@ {{#if isPrompt}} -
{{{localize "ITEM-PILES.Trade.Prompt.Content" trader_user_name=traderUser.name - trader_actor_name=traderActor.name}}}
+
{{{localize "ITEM-PILES.Trade.Prompt.Content" trading_user_name=tradingUser.name + trading_actor_name=tradingActor.name}}}
{{else}} @@ -68,6 +63,7 @@
+ {{log user}} {{#select user.id}} + {{#each actors as |actor id|}} + + {{/each}} + + + {{/select}} + {{else}} {{actor.name}} + {{/if}} +
{{else}}

{{ localize "ITEM-PILES.Trade.Request.DropActor" }}

{{/if}}
- {{#unless preselectedActor}} + {{#if hasUnlinkedTokenOwnership}}
- {{#unless isGM}} - {{#if multipleActors}} - {{#select actor.uuid}} - - {{/select}} - {{/if}} - {{/unless}}
- {{/unless}} + {{/if}}