misthero / dnd5e-spellpoints

FoundryVTT module for spellpoints magic system 5e
MIT License
8 stars 16 forks source link

[Feature Request] New Tidy 5e Sheets Compatibility #63

Open kgar opened 7 months ago

kgar commented 7 months ago

Hello, I am the author of the new Tidy 5e Sheets rewrite. The new sheets are using svelte and have their own rendering rules and HTML structure, so they do not integrate automatically the way default and default-like sheets do.

As a result, I have been going from module to module to request the opportunity to establish intentional compatibility with my sheets. I have an API for injecting content, and I will gladly volunteer a pull request and/or sample code for integrating.

Recently, I had a github issue requesting compatibility between our modules: https://github.com/kgar/foundry-vtt-tidy-5e-sheets/issues/247

If there is any content that is injected into the sheet during rendering, that would be what needs the API to be injected successfully. Also, I have special selectors available for injecting things.

For now, I just wanted to start the discussion, as I'm doing some compatibility work elsewhere, but I can provide you some neat options for where to put things like the "This character uses Spell Points" toggle.

Wherever possible, I try to make integration out-of-the-way and minimal, and my integration API / offerings are under active development and evolving based on module author needs.

I hope to work together. Our mutual users would love for us to team up.

misthero commented 7 months ago

hello @kgar , thanks for writing up, didn't know about the API, and that's awesome. I think I will need that for this and another module https://github.com/misthero/dnd5e-custom-skills.

That module adds skills and abilities and has been a litte hard to integrate it with tidy.

If you can throw a couple of hints at me, about where to start I hope I can figure out the rest.

kgar commented 7 months ago

Thanks for working with me on this.

Acquiring the API Instance

First, you acquire the API through a Tidy custom hook:

Hooks.once("tidy5e-sheet.ready", (api) => {
  // TODO: Add some awesome stuff!
});

Alternatively, and this is usually for the World Scripter crowd, the API can be acquired from the module, but there are caveats to this approach:

const api = game.modules.get('tidy5e-sheet')?.api ?? game.modules.get('tidy5e-sheet-kgar')?.api;

Caveats:

  1. tidy5e-sheet-kgar is my temporary module ID until I retire the original Tidy 5e Sheet repo and overtake the main module ID.
  2. The API is assigned to both modules, if they are present, but the real reason I have both module IDs listed is to account for the day when my module takes over, so that there's no disruption to scripts that are using this approach.
  3. The API is specifically available after Foundry ready is called.
kgar commented 7 months ago

Injecting Standalone Content

If you have content that is meant to be placed in one location, you can use the content registration functions.

Here is an example of registering content for a character:

Hooks.once("tidy5e-sheet.ready", (api) => {
  api.registerCharacterContent(
    new api.models.HtmlContent({
      html: `<a title="Example Button" class="my-custom-icon"><i class="fas fa-user"></i></a>`,
      injectParams: {
        selector: `[data-tidy-sheet-part="${api.constants.SHEET_PARTS.NAME_CONTAINER}"]`,
        position: "beforebegin",
      },
      onRender: (params) => {
        params.element
          .querySelector(".my-custom-icon")
          .addEventListener("click", () => alert("Clicked custom PC icon"));
      },
    })
  );
});

When you register content in this way, Tidy knows to clear this content and refresh it any time the default sheets would normally have re-rendered the entire sheet.

If your content has HTML inputs with the name attribute, changes to those inputs will automatically submit the sheets and save changes. Otherwise, if you normally have to wire events for your content, you can use onRender to wire events, as onRender is called after your content has been put in place.

I have deliberately chosen to use insertAdjacentHTML() as the underlying mechanism for placing custom content. As such, the injectParams prop's position field is referring to these locations from the related MDN article:

image

kgar commented 7 months ago

Sheet Parts

I'm on a quest to provide data-tidy-sheet-part attributes throughout my sheets so that most common injection sites are easily targetable. One reason for this is because classes can come and go, and I'm aiming to make multiple sheet layouts in the future, so I want attributes which are independent from potential CSS rule targeting. Additionally, I'll be using sheet parts when doing automated testing, so my aim for them to stick around and to be fairly reliable.

With that said, it's a work in progress. You'll see sheet parts throughout the sheets in the DOM:

image

Meanwhile, I also have a consolidated list of the sheet parts with general explanations in the API docs here: https://kgar.github.io/foundry-vtt-tidy-5e-sheets/classes/Tidy5eSheetsApi.html#constants

Below the constants section, each sheet part that currently exists in the wild has an explanation attached to it.

Need Specific Sheet Parts, Let Me Know πŸ™

I'm in the business of adding more sheet parts. I'm on a fairly short release cycle (multiple per day if I have to, but usually 1 release every 2-4 days). I will make a special effort if a module developer needs API stuff and I'm certain how to provide it.

kgar commented 7 months ago

That's what I have for you for now. Let me know if you have any questions, comments, ideas, etc. I should really start that wiki sometime soon, having typed some of this out here...

misthero commented 7 months ago

Thanks for the heads up, I tried the implementation, and the tidy5e-sheet.ready works well, but I think in my case I should keep using the .renderActorSheet hook, since I need the current actor document before adding the html. I need to know if the sheet is for a player character and read the actor flags to change the icon. Any idea?

kgar commented 7 months ago

I see what you mean.

You can use a callback function when registering HTML content, which should give you the flexibility you need:

Hooks.once("tidy5e-sheet.ready", (api) => {
  api.registerCharacterContent(
    new api.models.HtmlContent({
      html: (data) => { // πŸ‘ˆ `data` is the actor sheet context data that comes with `renderActorSheet`
        const myFlag = data.actor.flags['my-module.my-flag'];
        return myFlag ? `<span class="my-content">πŸ‘</span>` : `<span class="my-content">😱</span>`;
      },
      injectParams: {
        selector: `[data-tidy-sheet-part="${api.constants.SHEET_PARTS.NAME_CONTAINER}"]`,
        position: "beforebegin",
      },
      enabled: (data) => true, // πŸ‘ˆ If you only show this content based on a setting, you can check that here; `data` is the actor sheet context data
      onRender: (params) => {
        params.element
          .querySelector(".my-content")
          ?.addEventListener("click", () => alert("Clicked custom PC icon"));
      },
    })
  );
});

In the previous example with using api.registerCharacterContent, it is applying to Player Characters only. There are other options like registerNpcContent for NPCs, registerVehicleContent for vehicles, and registerActorContent for all supported Tidy actor sheets.

Umustalldie2 commented 3 months ago

Was there any update on this? I would love to see it work with tidy5e sheets

kgar commented 3 months ago

If it helps, Tidy has a tidy5e-sheet.renderActorSheet hook which gets called like the traditional renderActorSheet hooks. You can inject any content into the sheet from that hook, and as long as you put data-tidy-render-scheme="handlebars" on the top-most HTML elements you inject, Tidy will make sure to remove the old content and inject the new content, in the style of Handlebars.

Hooks.on('tidy5e-sheet.renderActorSheet', (app, element, data) => {
  const someHtml = `<h1 data-tidy-render-scheme="handlebars">Hello</h1>`;
  $('.some-class').append(someHtml);
});

If you want to reuse your template without putting Tidy attributes on it, you can wrap your result template code as follows:

let rendered_html = '<p>rendered template here</p>';
// When type is "tidy", wrap it
if (type === 'tidy') {
  rendered_html = `<div style="display: contents;" data-tidy-render-scheme="handlebars">${rendered_html}</div>`
}

I realize the API approach is a lot, so these days, I encourage using the tidy hook and the data-tidy-render-scheme attribute for injecting stuff into Tidy sheets.

misthero commented 3 months ago

Was there any update on this? I would love to see it work with tidy5e sheets

the module works on tidy (by my testing) just the fancy spell points tracking bar of the dndv3 character sheet is not there, but functionally it should calculate spell points correctly, make sure you updated to version 2.2.1 of the spell points module.

for now you can just flag as favourite the spell points item to see it in the first page of the character sheet.

kgar commented 3 months ago

The user who reported this on my repo retested and is good to go. I'm good for this issue to close if everyone here is.