From e725d3b9f327e6196a6eafcd16d4980a66973a68 Mon Sep 17 00:00:00 2001 From: Kevin Lee Garner Date: Wed, 24 Jul 2024 15:43:37 -0500 Subject: [PATCH] [#681] Assign Spells to Source Class (#685) * Added application for assigning spells to source classes. Added needed loc keys. Added Tools button menu to character / npc spellbook tab toolbars. * Removed TidyHooks comment. That's not the right place or foundry hooks that I'm using but not broadcasting. --- public/lang/en.json | 5 + .../SpellSourceClassAssignments.svelte | 147 ++++++++++++++++++ ...llSourceClassAssignmentsFormApplication.ts | 87 +++++++++++ src/foundry/foundry-adapter.ts | 3 + src/scss/core.scss | 18 ++- .../tabs/CharacterSpellbookTab.svelte | 23 +++ src/sheets/npc/tabs/NpcSpellbookTab.svelte | 26 ++++ 7 files changed, 308 insertions(+), 1 deletion(-) create mode 100644 src/applications/spell-source-class-assignments/SpellSourceClassAssignments.svelte create mode 100644 src/applications/spell-source-class-assignments/SpellSourceClassAssignmentsFormApplication.ts diff --git a/public/lang/en.json b/public/lang/en.json index a6178d974..bbb595369 100644 --- a/public/lang/en.json +++ b/public/lang/en.json @@ -808,8 +808,10 @@ "TIDY5E.ItemFilters.Filter.Other": "Other", "TIDY5E.GMOnly.Message": "GM Only: {message}", "TIDY5E.ReminderToBackUp": "Always remember to back up your world.", + "TIDY5E.Utilities.Tools": "Tools", "TIDY5E.Utilities.GMTools": "GM Tools", "TIDY5E.Utilities.IdentifyAll": "Identify All Items", + "TIDY5E.Utilities.AssignSpellsToClasses": "Assign Spells to Classes", "TIDY5E.GenericErrorNotification": "An error occurred while performing this action. See the devtools console for more details.", "TIDY5E.Utilities.MarkAllAsUnidentified": "Mark All Items as Unidentified", "TIDY5E.Utilities.ConfigureSections": "Configure Sections", @@ -830,5 +832,8 @@ "text": "Are you sure you wish to use default?" }, "TIDY5E.RollRecharge.Hint": "{rechargeLabel} (Shift+Click to skip the roll and instantly recharge) ", + "TIDY5E.SpellSourceClassAssignments.Identifier": "Source Class Identifier", + "TIDY5E.SpellSourceClassAssignments.IdentifierHint": "The spell's source class identifier appears here. To assign an identifier which the character doesn't own, enter the class identifier in this text field.", + "TIDY5E.SpellSourceClassAssignments.ShowUnassignedOnly.Text": "Show Unassigned Only", "TIDY5E.LocalizationTestKey": "Localization Test Key" } diff --git a/src/applications/spell-source-class-assignments/SpellSourceClassAssignments.svelte b/src/applications/spell-source-class-assignments/SpellSourceClassAssignments.svelte new file mode 100644 index 000000000..6b5821958 --- /dev/null +++ b/src/applications/spell-source-class-assignments/SpellSourceClassAssignments.svelte @@ -0,0 +1,147 @@ + + +
+ + +
diff --git a/src/applications/spell-source-class-assignments/SpellSourceClassAssignmentsFormApplication.ts b/src/applications/spell-source-class-assignments/SpellSourceClassAssignmentsFormApplication.ts new file mode 100644 index 000000000..41821e511 --- /dev/null +++ b/src/applications/spell-source-class-assignments/SpellSourceClassAssignmentsFormApplication.ts @@ -0,0 +1,87 @@ +import type { SvelteComponent } from 'svelte'; +import AssignSpellsToSourceClasses from './SpellSourceClassAssignments.svelte'; +import SvelteFormApplicationBase from '../SvelteFormApplicationBase'; +import type { Actor5e } from 'src/types/types'; +import { writable, type Writable } from 'svelte/store'; +import type { Item5e } from 'src/types/item.types'; +import { CONSTANTS } from 'src/constants'; +import { StoreSubscriptionsService } from 'src/features/store/StoreSubscriptionsService'; +import { FoundryAdapter } from 'src/foundry/foundry-adapter'; + +export type SpellSourceClassAssignment = { + /** + * The spell to receive an assignment. + */ + item: Item5e; + /** + * Represents the chosen source class. + */ + sourceClass: string; +}; + +export type SpellSourceClassAssignmentsContext = { + actor: Actor5e; + assignments: SpellSourceClassAssignment[]; +}; + +export default class SpellSourceClassAssignmentsFormApplication extends SvelteFormApplicationBase { + context: Writable = writable(); + actor: Actor5e; + subscriptionsService: StoreSubscriptionsService; + updateHook: number | undefined; + + constructor(actor: Actor5e, ...args: any[]) { + super(...args); + this.actor = actor; + this.subscriptionsService = new StoreSubscriptionsService(); + } + + createComponent(node: HTMLElement): SvelteComponent { + this.context.set(this.getData()); + + return new AssignSpellsToSourceClasses({ + target: node, + context: new Map([ + ['appId', this.appId], + ['context', this.context], + ]), + }); + } + + getData(): SpellSourceClassAssignmentsContext { + return { + actor: this.actor, + assignments: this.actor.items + .filter((item: Item5e) => item.type === CONSTANTS.ITEM_TYPE_SPELL) + .map((item: Item5e) => ({ + item, + sourceClass: 'test', + })), + }; + } + + activateListeners(html: any): void { + Hooks.off('updateItem', this.updateHook); + this.trackActorChanges(); + super.activateListeners(html); + } + + private trackActorChanges() { + this.updateHook = Hooks.on('updateItem', (item: Item5e) => { + if (item.actor?.id !== this.actor.id) { + return; + } + + this.context.set(this.getData()); + }); + } + + get title() { + return FoundryAdapter.localize('TIDY5E.Utilities.AssignSpellsToClasses'); + } + + close(options: unknown = {}) { + Hooks.off('updateItem', this.updateHook); + return super.close(options); + } +} diff --git a/src/foundry/foundry-adapter.ts b/src/foundry/foundry-adapter.ts index 019c4bf66..1b4cbbab6 100644 --- a/src/foundry/foundry-adapter.ts +++ b/src/foundry/foundry-adapter.ts @@ -884,6 +884,9 @@ export const FoundryAdapter = { true ); }, + async renderSheetFromUuid(uuid: string) { + (await fromUuid(uuid))?.sheet?.render(true); + }, renderImagePopout(...args: any[]) { return new ImagePopout(...args).render(true); }, diff --git a/src/scss/core.scss b/src/scss/core.scss index d45cabb1e..595e0738b 100644 --- a/src/scss/core.scss +++ b/src/scss/core.scss @@ -290,7 +290,7 @@ border: 0.125rem solid var(--t5e-separator-color); border-radius: 0.25rem; box-shadow: 0 0 0.25rem var(--dnd5e-shadow-45); - + + li { margin-top: 0.25rem; } @@ -379,6 +379,22 @@ margin: 0.25rem 0; } } + + /* Spacing utilities */ + + // TODO: "draw more inspiration" from https://getbootstrap.com/docs/5.3/utilities/spacing/ + .p-1 { + padding: 0.25rem; + } + + /* Text utilities */ + .capitalize { + text-transform: capitalize; + } + + .semibold { + font-weight: 500; + } } @import './partials/floating-context-menu'; diff --git a/src/sheets/character/tabs/CharacterSpellbookTab.svelte b/src/sheets/character/tabs/CharacterSpellbookTab.svelte index 27f8af4f4..7bf5d884c 100644 --- a/src/sheets/character/tabs/CharacterSpellbookTab.svelte +++ b/src/sheets/character/tabs/CharacterSpellbookTab.svelte @@ -24,6 +24,9 @@ import { TidyFlags } from 'src/foundry/TidyFlags'; import { SheetSections } from 'src/features/sections/SheetSections'; import { ItemVisibility } from 'src/features/sections/ItemVisibility'; + import ButtonMenu from 'src/components/button-menu/ButtonMenu.svelte'; + import ButtonMenuCommand from 'src/components/button-menu/ButtonMenuCommand.svelte'; + import SpellSourceClassAssignmentsFormApplication from 'src/applications/spell-source-class-assignments/SpellSourceClassAssignmentsFormApplication'; let context = getContext>( CONSTANTS.SVELTE_CONTEXT.CONTEXT, @@ -100,6 +103,26 @@ )} /> + + { + new SpellSourceClassAssignmentsFormApplication($context.actor).render( + true, + ); + }} + iconClass="fas fa-list-check" + disabled={!$context.editable} + > + {localize('TIDY5E.Utilities.AssignSpellsToClasses')} + + {#each utilityBarCommands as command (command.title)} >( CONSTANTS.SVELTE_CONTEXT.CONTEXT, @@ -49,6 +53,8 @@ $: utilityBarCommands = $context.utilities[tabId]?.utilityToolbarCommands ?? []; + + const localize = FoundryAdapter.localize; @@ -62,6 +68,26 @@ )} /> + + { + new SpellSourceClassAssignmentsFormApplication($context.actor).render( + true, + ); + }} + iconClass="fas fa-list-check" + disabled={!$context.editable} + > + {localize('TIDY5E.Utilities.AssignSpellsToClasses')} + + {#each utilityBarCommands as command (command.title)}