27 Replies
Nekro Darkmoon
Nekro DarkmoonOP15mo ago
<script>
import { getContext, onDestroy } from "svelte";

import { ApplicationShell } from "#runtime/svelte/component/core";
import { TJSDocument } from "#runtime/svelte/store/fvtt/document";
import { DynMapReducer } from "#runtime/svelte/store/reducer";
import { TJSInput } from "#standard/component";

import CompendiumFilters from "../components/CompendiumFilters.svelte";
import CompendiumItemList from "../components/CompendiumItemList.svelte";
import Spinner from "../components/Spinner.svelte";

import {
addTJSDocumentSearchFilter,
removeSearchFilter,
} from "../handlers/handleSearchFilter";

async function getDocuments(docList) {
// Sort the documents into alphabetical order
docList.sort((a, b) => a.name.localeCompare(b.name));

const validDocs = new Map();

// Create a TJSDocument for each document in the compendium. Filter out invalid docs.
await Promise.all(
docList.map(async ({ _id, uuid }) => {
const doc = new TJSDocument();
const setSuccessfully = await doc.setFromUUID(uuid);

if (setSuccessfully) validDocs.set(_id, doc);
})
);

return validDocs;
}

export let { compendiumType, document, sheet } =
getContext("#external").application;

export let elementRoot;

let reducer = new DynMapReducer();
let loading = true;

const searchInput = addTJSDocumentSearchFilter(reducer);
onDestroy(() => removeSearchFilter(reducer));

// Fake an async operation in svelte to get around the fact that we can't await in the script tag
Promise.resolve(getDocuments([...document.index])).then((docs) => {
reducer.setData(docs, true);
reducer.index.update();
loading = false;
});
</script>

<ApplicationShell bind:elementRoot>
<main>
<CompendiumFilters {compendiumType} />

<div class="search-field">
<TJSInput
input={searchInput}
--tjs-input-placeholder-color="#555"
--tjs-input-text-margin="0"
--tjs-input-text-width="100%"
/>
</div>

{#if loading}
<div class="spinner-wrapper">
<Spinner />
</div>
{:else}
<CompendiumItemList documents={[...$reducer]} {compendiumType} />
{/if}
</main>
</ApplicationShell>
<script>
import { getContext, onDestroy } from "svelte";

import { ApplicationShell } from "#runtime/svelte/component/core";
import { TJSDocument } from "#runtime/svelte/store/fvtt/document";
import { DynMapReducer } from "#runtime/svelte/store/reducer";
import { TJSInput } from "#standard/component";

import CompendiumFilters from "../components/CompendiumFilters.svelte";
import CompendiumItemList from "../components/CompendiumItemList.svelte";
import Spinner from "../components/Spinner.svelte";

import {
addTJSDocumentSearchFilter,
removeSearchFilter,
} from "../handlers/handleSearchFilter";

async function getDocuments(docList) {
// Sort the documents into alphabetical order
docList.sort((a, b) => a.name.localeCompare(b.name));

const validDocs = new Map();

// Create a TJSDocument for each document in the compendium. Filter out invalid docs.
await Promise.all(
docList.map(async ({ _id, uuid }) => {
const doc = new TJSDocument();
const setSuccessfully = await doc.setFromUUID(uuid);

if (setSuccessfully) validDocs.set(_id, doc);
})
);

return validDocs;
}

export let { compendiumType, document, sheet } =
getContext("#external").application;

export let elementRoot;

let reducer = new DynMapReducer();
let loading = true;

const searchInput = addTJSDocumentSearchFilter(reducer);
onDestroy(() => removeSearchFilter(reducer));

// Fake an async operation in svelte to get around the fact that we can't await in the script tag
Promise.resolve(getDocuments([...document.index])).then((docs) => {
reducer.setData(docs, true);
reducer.index.update();
loading = false;
});
</script>

<ApplicationShell bind:elementRoot>
<main>
<CompendiumFilters {compendiumType} />

<div class="search-field">
<TJSInput
input={searchInput}
--tjs-input-placeholder-color="#555"
--tjs-input-text-margin="0"
--tjs-input-text-width="100%"
/>
</div>

{#if loading}
<div class="spinner-wrapper">
<Spinner />
</div>
{:else}
<CompendiumItemList documents={[...$reducer]} {compendiumType} />
{/if}
</main>
</ApplicationShell>
So this would be our current usecase which is to get a list of all the available documents from a compendium I guess in this particular use case we don't really need the documents to be reactive since compendium don't really change. But we have other bits in the system that are a collection of different documents that need to keep track of changing data.
TyphonJS (Michael)
This definitely brings up a possible inclusion of more utility helpers to TRL for compendium access as there is nothing available currently. As you mention quite likely wrapping each underlying compendium document in a TJSDocument instance is not necessary. Figuring out the best way to proceed w/ dynamic reducers (reactive embedded collections, etc.) and reactive document content for all documents being reduced will be interesting given the general constraints imposed by the Foundry core platform. Avoiding heavyweight / potentially error prone solutions (dangling listeners) is relatively tough or at least needs quite a bit of testing.
Nekro Darkmoon
Nekro DarkmoonOP15mo ago
yeah
TyphonJS (Michael)
I definitely appreciate the exploration angle w/ all of this I'd like to start rounding out convenience / helper code particularly for system devs in the Oct - Dec TRL dev push. In Auckland airport so back home soon; a couple of days until things start in earnest. If you have the time to catalog these general patterns across your system efforts it's something; I'd like to get a list of use cases for potential easy to use solutions.
Nekro Darkmoon
Nekro DarkmoonOP15mo ago
Can try to that 😄 Currently trying to use SvelteApplication to set up something of a placeableHud 🤔
TyphonJS (Michael)
Hmm.. Certainly check out the Position Basic demo using EmptyApplicationShell (I think) but that sounds a bit challenging if trying to sync up w/ canvas position around / near a token. IE canvas pans being pretty hard to accommodate; much easier w/out canvas movement.
Nekro Darkmoon
Nekro DarkmoonOP15mo ago
currently I only need it to be in one position
Nekro Darkmoon
Nekro DarkmoonOP15mo ago
I've had good luck with using the chat message rendering technique to modify the effects panel with svelte
No description
Nekro Darkmoon
Nekro DarkmoonOP15mo ago
but for my current purposes there's no existing element already being rendered so that approach is not quite feasible. I could potentiall render an empty element and hook into that but I'd like to try to use a base application if possible for this one
TyphonJS (Michael)
Yeah this demo in essential-svelte-esm likely is relevant if going for a full SvelteApplication approach. https://github.com/typhonjs-fvtt-demo/essential-svelte-esm/tree/main/src/view/position/basic-overlay
Nekro Darkmoon
Nekro DarkmoonOP15mo ago
👌 ty ah EmptyApplicationShell is what I needed 😄
TyphonJS (Michael)
Also don't overlook TJSContextMenu / use a single custom component in the data defined menu if creating a unique hud / menu that should automatically handle closing when focus is lost. This would be something responding to a click of a token HUD button. https://github.com/typhonjs-fvtt-lib/svelte-standard/blob/main/src/application/TJSContextMenu.js
Nekro Darkmoon
Nekro DarkmoonOP15mo ago
I've not yet gotten the chance or had the need to use a ContextMenu but hopefully in the future 🤞 I have however used TJSMenu a lot 😄 I think the only problem I have with it though is that its hard to run an inspect element on it cz it loses focus and closes as soon as I click the inspect button 🤣
TyphonJS (Michael)
Very similar, but could be used potentially for a button press of a token HUD; not actually for context clicking.
Nekro Darkmoon
Nekro DarkmoonOP15mo ago
true
TyphonJS (Michael)
As long as you are doing production builds you could modify the associated component and comment out this line temporarily: https://github.com/typhonjs-fvtt-lib/svelte-standard/blob/main/src/component/standard/menu/context/TJSContextMenuImpl.svelte / line 387 needs to be commented out.
Nekro Darkmoon
Nekro DarkmoonOP15mo ago
🤔 that should help a lot ❤️ tyvm Btw can I use TJSPosition to track the position of a non svelte app 🤔 I've been doing new TJSPosition(document.querySelector('#ui-right')) but all the values in it are null
TyphonJS (Michael)
I assume the #ui-right is the Foundry containing div for the sidebar. That is not what TJSPosition is for exactly especially since #ui-right is a div controlled by static CSS. It controls inline styles, but doesn't monitor things. There is a Svelte action for optimized resize observation, but not separated to use independently of the action. Basically, you'll need to use a ResizeObserver instance / build a custom solution for non-Svelte usage. You can get some ideas from the action: https://github.com/typhonjs-svelte/runtime-base/blob/main/src/svelte/action/dom/resizeObserver.js
Nekro Darkmoon
Nekro DarkmoonOP15mo ago
hmmm i'll chck it out
TyphonJS (Michael)
Should be import { resizeObserver } from '#runtime/svelte/action/dom';
Nekro Darkmoon
Nekro DarkmoonOP15mo ago
const sidebarHook = Hooks.on("collapseSidebar", () => {
sidebarStartPosition = getRightPosition();
});

function getRightPosition() {
const uiRight = document.querySelector("#ui-right");
const uiRightRect = uiRight.getBoundingClientRect();
return uiRightRect.left;
}

onDestroy(() => {
console.log("Destroying A5EEffectsPanel");
Hooks.off("collapseSidebar", sidebarHook);
});

let sidebarStartPosition = getRightPosition();

const position = application.position;
const panelWidth = position.stores.width;
position.stores.top.set(20);

$: position.stores.left.set(sidebarStartPosition - $panelWidth - 5);
const sidebarHook = Hooks.on("collapseSidebar", () => {
sidebarStartPosition = getRightPosition();
});

function getRightPosition() {
const uiRight = document.querySelector("#ui-right");
const uiRightRect = uiRight.getBoundingClientRect();
return uiRightRect.left;
}

onDestroy(() => {
console.log("Destroying A5EEffectsPanel");
Hooks.off("collapseSidebar", sidebarHook);
});

let sidebarStartPosition = getRightPosition();

const position = application.position;
const panelWidth = position.stores.width;
position.stores.top.set(20);

$: position.stores.left.set(sidebarStartPosition - $panelWidth - 5);
This was the solution I came up with earlier going to try to see if the resizeObserver will work better but yeah we've been trying to fit svelte in whereever possible 🤣
Nekro Darkmoon
Nekro DarkmoonOP15mo ago
No description
TyphonJS (Michael)
What are you attempting to do w/ the sidebar constraints?
Nekro Darkmoon
Nekro DarkmoonOP15mo ago
Nekro Darkmoon
Nekro DarkmoonOP15mo ago
essentially having it stick to the sidebar imo there are 3 major hurdles I have to tackle for this project - Rendering the empty application ✅ - Sticking the rendered application to the left of the sidebar ❓ - Having a TJS document for the selected token and having it automatically change on selecting a different token
TyphonJS (Michael)
So in FQL I have an app dock to the sidebar; this is the quest tracker. With TJSPosition you can set a "validator". I haven't updated this code in over a year as it was the initial porting test to TRL, but the early TRL solution for defining this validator is here and should help: https://github.com/typhonjs-fvtt/typhonjs-quest-log/blob/master/src/view/tracker2/PositionValidator.js And you can see usage in the constructor here which all still works given import statements are wrong for latest TRL: https://github.com/typhonjs-fvtt/typhonjs-quest-log/blob/master/src/view/tracker2/QuestTrackerApp.js So with TJSPosition you can do some really fancy things that I'm sure just myself is aware about. Alas kind of hard to provide this kind of solution for Foundry as it would take a lot of work to make it play nice between different packages.
Nekro Darkmoon
Nekro DarkmoonOP15mo ago
hmmm interesting
Want results from more Discord servers?
Add your server