Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for changing prepared quantities from the formula picker #17899

Merged
merged 1 commit into from
Jan 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 92 additions & 15 deletions src/module/actor/character/apps/formula-picker/app.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import HoverIconButton from "@module/sheet/components/hover-icon-button.svelte";
import { sendItemToChat } from "@module/sheet/helpers.ts";

const { state: data, actor, ability, searchEngine, onSelect, onDeselect }: FormulaPickerContext = $props();
const { state: data, actor, ability, mode, searchEngine, onSelect, onDeselect }: FormulaPickerContext = $props();
const openStates: Record<string, boolean> = $state({});
let queryText = $state("");

Expand Down Expand Up @@ -44,7 +44,11 @@
<header>{game.i18n.format("PF2E.LevelN", { level: section.level })}</header>
<ol class="items-list">
{#each section.formulas as formula (formula.item.id)}
<li class="item" class:selected={formula.selected}>
<li
class="item"
class:selected={formula.selected}
class:faded={mode === "prepare" && !formula.selected}
>
<HoverIconButton
class="item-image"
src={formula.item.img}
Expand All @@ -59,15 +63,54 @@
<span>{formula.item.name}</span>
<ItemTraits traits={formula.item.traits} rarity={formula.item.rarity} />
</button>
<button
class="select"
class:selected={formula.selected}
onclick={() => (formula.selected ? onDeselect(formula.item.uuid) : onSelect(formula.item.uuid))}
aria-labelledby="tooltip"
data-tooltip={formula.selected ? "Cancel" : "Confirm"}
>
<i class="fa-solid fa-fw fa-check"></i>
</button>
{#if mode === "prepare"}
<div class="quantity">
<button
type="button"
class="subtract"
disabled={formula.quantity === 0}
data-tooltip="PF2E.Actor.Character.Crafting.DecreaseQuantity"
aria-labelledby="tooltip"
onclick={() => ability.setFormulaQuantity(formula.item.uuid, "decrease")}
>
<i class="fa-solid fa-minus"></i>
</button>
<input
class:selected={formula.selected}
type="number"
placeholder="0"
value={formula.quantity || null}
onblur={(event) => {
ability.setFormulaQuantity(
formula.item.uuid,
Number(event.currentTarget.value || 0),
);
event.currentTarget.value = formula.quantity || null;
}}
/>
<button
type="button"
class="plus"
data-tooltip="PF2E.Actor.Character.Crafting.IncreaseQuantity"
aria-labelledby="tooltip"
onclick={() => ability.setFormulaQuantity(formula.item.uuid, "increase")}
>
<i class="fa-solid fa-plus"></i>
</button>
</div>
{:else}
<button
type="button"
class="select"
class:selected={formula.selected}
onclick={() =>
formula.selected ? onDeselect(formula.item.uuid) : onSelect(formula.item.uuid)}
aria-labelledby="tooltip"
data-tooltip={formula.selected ? "Cancel" : "Confirm"}
>
<i class="fa-solid fa-fw fa-check"></i>
</button>
{/if}
<ItemSummary
uuid={formula.item.uuid}
open={!!openStates[formula.item.uuid]}
Expand Down Expand Up @@ -96,7 +139,7 @@
width: 100%;
flex: 1;
overflow-y: scroll;
padding: var(--space-8);
padding: var(--space-8) 0;
padding-top: 0;

header {
Expand All @@ -105,6 +148,7 @@
margin-bottom: var(--space-2);
font-weight: 500;
font-size: var(--font-size-16);
padding: 0 var(--space-8);
}

ol {
Expand All @@ -123,11 +167,17 @@
flex-direction: row;
flex-wrap: wrap;
width: 100%;
padding: var(--space-4) 0;
padding: var(--space-4) var(--space-8);
margin: 0;

&.selected .name > span {
font-style: italic;
&.faded {
> *:not(.item-summary) {
opacity: 0.88;
}

> :global(.item-image img) {
filter: grayscale(0.85);
}
}

> :global(.item-image) {
Expand All @@ -140,9 +190,11 @@
cursor: pointer;
flex: 1;
margin: 0;

&:hover {
text-shadow: 0 0 8px var(--color-shadow-primary);
}

:global {
.tags {
padding: 0;
Expand All @@ -154,6 +206,31 @@
}
}

> .quantity {
display: flex;
margin-top: var(--space-6);
button {
width: 1.375rem;
height: 1.375rem;
i {
margin: 0;
}
}
input {
border: none;
width: 3ch;
height: 1.375rem;
padding-left: 0;
padding-right: 0;
text-align: center;
&.selected {
font-weight: 600;
opacity: unset;
color: var();
}
}
}

> button.select {
width: 1.75rem;
height: 1.75rem;
Expand Down
77 changes: 48 additions & 29 deletions src/module/actor/character/apps/formula-picker/app.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { ActorPF2e } from "@actor/base.ts";
import type { CraftingAbility } from "@actor/character/crafting/ability.ts";
import { CraftingFormula } from "@actor/character/crafting/types.ts";
import { CharacterPF2e } from "@actor/character/document.ts";
import { ResourceData } from "@actor/creature/index.ts";
import { AbilityItemPF2e, FeatPF2e, PhysicalItemPF2e } from "@item";
Expand All @@ -16,11 +15,8 @@ import Root from "./app.svelte";
interface FormulaPickerConfiguration extends ApplicationConfiguration {
actor: CharacterPF2e;
ability: CraftingAbility;
prompt: string;
item?: FeatPF2e | AbilityItemPF2e;
getSelected?: () => ItemUUID[];
onSelect: SelectFunction;
onDeselect: SelectFunction;
mode: "craft" | "prepare";
}

/** Creates a formula picker dialog that resolves with the selected item */
Expand Down Expand Up @@ -85,48 +81,71 @@ class FormulaPicker extends SvelteApplicationMixin<
}

protected override async _prepareContext(): Promise<FormulaPickerContext> {
const actor = this.options.actor;
const ability = this.options.ability;
const resource = actor.getResource(ability.resource ?? "");
const selected = this.options.getSelected?.() ?? [];

const formulas = (await actor.crafting.getFormulas()).filter((f) => ability.canCraft(f.item, { warn: false }));
const { actor, ability, mode } = this.options;
const formulas = await ability.getValidFormulas();
const sheetData = await ability.getSheetData();
const resource = sheetData.resource;
this.#searchEngine.removeAll();
this.#searchEngine.addAll(formulas.map((f) => R.pick(f.item, ["id", "name"])));

const prompt =
mode === "prepare"
? game.i18n.format("PF2E.Actor.Character.Crafting.PrepareHint", {
remaining: sheetData.remainingSlots,
})
: resource
? game.i18n.format("PF2E.Actor.Character.Crafting.Action.Hint", {
resource: resource.label,
value: resource.value,
max: resource.max,
})
: game.i18n.localize("PF2E.Actor.Character.Crafting.Action.HintResourceless");

return {
foundryApp: this,
actor,
ability,
mode: this.options.mode,
onSelect: (uuid: ItemUUID) => {
if (this.#resolve) {
const item = formulas.find((f) => f.item.uuid === uuid)?.item;
this.selection = item ?? null;
this.close();
} else if (mode === "prepare") {
ability.prepareFormula(uuid);
}

this.options.onSelect(uuid, { formulas });
},
onDeselect: (uuid: ItemUUID) => {
this.options.onDeselect(uuid, { formulas });
if (mode === "prepare") {
ability.unprepareFormula(uuid);
}
},
searchEngine: this.#searchEngine,
state: {
name: this.options.item?.name ?? ability.label,
resource,
prompt: this.options.prompt,
prompt,
sections: R.pipe(
formulas.map((f) => ({
item: {
...R.pick(f.item, ["id", "uuid", "img", "name"]),
type: f.item.type as ItemType,
level: f.item.level,
rarity: f.item.rarity,
traits: f.item.traitChatData(),
},
batchSize: f.batchSize,
selected: selected.includes(f.item.uuid),
})),
formulas.map((f) => {
const preparedQuantity =
mode === "prepare"
? ability.preparedFormulaData
.filter((d) => d.uuid === f.item.uuid)
.reduce((sum, v) => sum + (v.quantity ?? 1), 0)
: 0;

return {
item: {
...R.pick(f.item, ["id", "uuid", "img", "name"]),
type: f.item.type as ItemType,
level: f.item.level,
rarity: f.item.rarity,
traits: f.item.traitChatData(),
},
quantity: mode === "prepare" ? preparedQuantity : f.batchSize,
selected: preparedQuantity > 0,
};
}),
R.groupBy((f) => f.item.level || 0),
R.entries(),
R.map(([level, formulas]): FormulaSection => ({ level: Number(level), formulas })),
Expand All @@ -140,6 +159,7 @@ class FormulaPicker extends SvelteApplicationMixin<
interface FormulaPickerContext extends SvelteApplicationRenderContext {
actor: ActorPF2e;
ability: CraftingAbility;
mode: "craft" | "prepare";
onSelect: (uuid: ItemUUID) => void;
onDeselect: (uuid: ItemUUID) => void;
searchEngine: MiniSearch<Pick<PhysicalItemPF2e, "id" | "name">>;
Expand All @@ -155,7 +175,8 @@ interface FormulaSection {
level: number;
formulas: {
item: FormulaViewData;
batchSize: number;
/** The batch size or quantity prepared depending on context */
quantity: number;
selected: boolean;
}[];
}
Expand All @@ -171,7 +192,5 @@ interface FormulaViewData {
rarity: Rarity | null;
}

type SelectFunction = (uuid: ItemUUID, options: { formulas: CraftingFormula[] }) => void;

export { FormulaPicker };
export type { FormulaPickerContext };
Loading
Loading