Sidebar Tab Application
I have, after diving deep into the Foundry Code, figured out how to actually add new Sidebar tabs.
it's a two step process of sorts.
You need to add the actual button to reveal the Sidebar Tab, as well as actually provide the SidebarTab Application.
Only ways to add the Button is either wrap the getData() method on Sidebar, if you already use libWrapper this is the cleaner method. Alternatively you can just sort of inject the HTML for the button with jQuery.
The Tab itself is actually the easier part. Because the canonical way is already just at the end of the SidebarTab constructor.
And then the Sidebar takes care of rendering and stuff.
However, the SidebarTab has a very specific api that the Sidebar Application expects to exist. As such, the Application templates in TRL are currently insufficient to just make new SidebarTabs on their own
37 Replies
As long as you add your SidebarTab class to
CONFIG.ui
at literally any point before the setup hook is called, it will get treated like any other SidebarTab, except for the missing buttonThere isn't too much logic in
SidebarTab
, so you can implement what is necessary as a child class to SvelteApplication.
There is no ETA currently when I might provide a ready-made solution. Perhaps any effort on your side could turn into said ready-made solution. 😄
There is a mechanism to switch the element root in SvelteApplication. This would come in handy when implementing the docked vs popout modes. You can use ApplicationShell
when in popout mode and a docked component otherwise.
An example of this is in my unreleased "Better Macro Directory" demo module. It switches between ApplicationShell / TJSApplicationShell for testing this mechanism though I haven't finished the ApplicationShell / light mode styles. This is done for a dark mode / native app look and feel test.
https://github.com/typhonjs-fvtt/better-macros-directory/blob/main/src/view/BMDAppShell.svelte#L20
https://github.com/typhonjs-fvtt/better-macros-directory/blob/main/src/view/BMDAppShell.svelte#L30-L34
https://github.com/typhonjs-fvtt/better-macros-directory/blob/main/src/view/BMDAppShell.svelte#L53-L55
Quite likely though you don't need the above and just have to launch another SvelteApplication w/ ApplicationShell wrapping your sidebar content component when the sidebar tab is right clicked. Store the reference to the popped out app in the sidebar instance and if tab right clicked again call bringToTop
instead of creating a new pop out.Oh yeah, i just have to figure out which of these methods actually matter and reimplement them.
I can't just copy the foundry code because that would violate the license
Not sure if that is the case if your code is only being run on Foundry. I believe there is some leniency for small utility aspects, but a lot of it can be rewritten. You can always ask / contact them directly or in the developer channels on the mothership.
@mleahy Okay so I've finally had some time to sit down and do this. Wierdly, I can't get the application to render docked. It always appears popped out
You need to use your own component beside
ApplicationShell
for the docked sidebar. Essentially duplicating similar layout to one of the classes that extends the SidebarTab class w/ a template like (SidebarDirectory): templates/sidebar/document-directory.html
.
The JS implementation of your SidebarTab class should be able to hold an instance of the SvelteApplication that uses ApplicationShell
to wrap whatever content is in the tab.is there a way to make use of getData with SvelteApplication?
For what purpose? In the App v1 API that is used to gather data to hand to the HBS template.
I'm trying to write the base class as a SvelteApplication version of SidebarTab
As such I want to keep the variability
SidebarTab has 4 variables that are passed to the template
cssId, cssClass and tabName are the ones that immediately matter
it also passes game.user
but that matters less with svelte
Yeah... You have access to the external application that the css ID / classes assigned in
defaultOptions
with
const { application } = getContext('#external');
You could store an additional parameter for tabName in the options.
You can actually access game
directly in Svelte templates as there is a special store to do so:
import { gameState } from '@typhonjs-fvtt/runtime/store';
Then in the template use $gameState.user
.
That last bit probably wouldn't be obvious, but just a handy utility store to access game
in a template. You can always access game
in the <script>
section.
Check out how ApplicationShell
uses the options from the external app options to set CSS ID / classes:
https://github.com/typhonjs-fvtt-lib/svelte/blob/main/src/component/core/application/ApplicationShell.svelte#L374-L375I'm still having the issue of actually getting the svelte component to render at the correct position
It just renders to document root
Creating a standardized SidebarTab implementation is something useful to come up w/ an official wrapped / basic implementation that can be reused in the future. Kind of like providing a standardized TRL / Svelte ActorSheet.
Not the document-fragment created in the tab
it should show up up there, but it just kind of gets appended
If I give it the same ID as my chat tab, it just refuses to render in the first place
You need to change the
target
parameter to #sidebar
instead of document.body
or provide the specific element.
----
I can actually look into things this weekend as setting up the sidebar Svelte wrapper doesn't require more work per se like the actor sheet will. It does look like you'll need to override the Foundry Sidebar
class to provide additional info on the new tabs to add in getData
.I also honestly have NO idea how those templates get filled, I#ve been trying to analyse it, but its just, boom, different
You shouldn't override it
It's a better idea to monkeypatch the function specifically
libWrapper is excellent for that exact purpose
Also, you've got to get the app rendered too, the best way to do so is to stick it into
CONFIG.ui
Then Sidebar will just call _render during its own _render calllibWrapper
should be avoided if at all possible. Just need to replace the getData
aspect returning a more complete list of tabs. I'll look into things this weekend though, so I'm just kind of spitballing right now.It's much more unsafe and prone for conflict to tear out the entire sidebar and replace the class than it is to use libWrapper to patch the function call
There can be no dependency in TRL /
svelte-standard
on libWrapper.That said, you can also forgo this entire workaround and just plug the tab in yourself
I'll see if I can come up with a solution. The Sidebar class only renders once so that most likely is possible.
as long as the
<nav>
element contains the button and the parent sidebar div contains the section with the corresponding data-tab property, it will work
So you could feasibly just write two svelte components that render a button and a section with the required contents and just target the corresponding elements on the page
The only downside is that you loose all the SidebarTab specific APII'm mostly concerned about providing a simple API to do so for module / system developers that handles what is necessary to inject the data into the Sidebar class and Foundry rendered template.
The SidebarTab specific API needs to be duplicated. As mentioned I'll take a look this weekend.
Yea yea, no worries, I'm just sharing what I have found about how the sidebar works to hopefully give you a headstart
It shouldn't be too bad and will likely work out only because
Sidebar
is only rendered once.Yeah
I wouldn't replace the Sidebar application itself though, as that might cause issues with modules that do use libWrapper to modify it
Yep, not mentioning replacing the Sidebar application.
Okay then I misunderstood
Just some initial spitballing.
I should mention, the only reason I am using libWrapper is to get the tab button rendered
because the buttons are hardcoded, the applications themselves interestingly aren't
The way those get added to the sidebar is actually in the constructor of the SidebarTab class
Literally like this:
ui.sidebar.tabs[this.tabName] = this
So if you were to just mount the button and rendered tab manually, you can avoid modifying getData() altogetherYep.. It will involve manipulating
ui.sidebar.tabs
. No modifying of getData
.The assignment of
ui.sidebar.tabs
also has to happen by the time the renderSidebar hook is called. I simply decided to stick my application class into CONFIG.ui in the init hook, which will automatically call the constructor before rendering the sidebarWell I'll definitely keep you informed on what I come up with.
I'm looking forward to it. For now, I'll simply develop the popout version of my component and take care of the sidebar once you've had time to figure it out
That said, this foray into TRL has taught me a lot though and the library has grown on me, I might migrate my other module over to TRL when I have the time
Modules for sure are open game for TRL presently w/ no limitations really. Getting the system specific helper APIs worked out is the stuff that will develop over a bit more time. Heh heh.. Glad to know that things are growing on you / making sense.
Making good progress... Initial engineering test / analysis efforts indicate that I should get this finished over the weekend.
Awesome!