#AppV2 Vue
1 messages · Page 1 of 1 (latest)
I have tried a few things using Vite and Rollup to build, briefly with Vue but mostly Svelte, but didnt manage to render a DocumentSheetV2 or anything really. If you have some directions or a working example that would help me greatly.
I haven't tried it with a bite or roll-up yet. I am just using the esm versions of Vue and pinia. But it shouldn't be all that much different to do it using vite. I was working on my GF mother car today and have D&D tomorrow so I'll be working on it more Monday.
For the document class itself, did you do something different? I was particularly confused about HandlebarsApplicationMixins
I wrote my own ApplicationMixin called VueApplicationMixin that basically injects the Vue component in as a part
@blissful nebula This is kinda my first attempt and its mostly focused on using SFC loader. I am going to fine tune it over the week. But if you are interested in taking a look, your more then welcome to.
Please note the reason its so focused on SFC, is I am experimenting with implementing Vue without a build step. This will make the module far less performant, which isn't highly recommened. I do plan to update it so that it works with vite and assumes your are compiling your module. But I tend to use Foundry as a playground for me to expirement and play in. I would suggest not using SFC for complex modules.
the only reason I am sharing this now, is that I am quite busy in my life, so I figured I would give you something to jump off of if you are interested as I can'[t guartee when I will finish this.
Alright, so I pulled out the SFC and loading of .vue files, and now have two files VueApplicationMixin.mjs and VueGetTemplate.mjs If you are using a build step, you can do
import Module from "./module.mjs";
import { createApp } from "vue";
import App from "./templates/App.vue";
import { VueApplicationMixin } from './assets/scripts/VueApplicationMixin.mjs';
const { ApplicationV2 } = foundry.applications.api;
class VueApplication extends VueApplicationMixin(ApplicationV2) {
static DEFAULT_OPTIONS = foundry.utils.mergeObject(super.DEFAULT_OPTIONS, {
id: `app-${Math.random().toString(36).substr(2, 9)}`,
window: {
title: `${Module.id}.title`,
icon: "fa-solid fa-triangle-exclamation"
},
position: {
width: 680,
height: "auto"
},
actions: { }
}, { inplace: false });
static PARTS = {
app: {
id: "app",
template: App,
scrollable: []
}
}
}
Hooks.once("ready", () => {
new VueApplication().render(true);
});
And if you are not using a build step, you do:
import Module from "./module.mjs";
import { VueApplicationMixin } from './libs/VueApplicationMixin.mjs';
import { vueGetTemplate } from './libs/VueGetTemplate.mjs';
const { ApplicationV2 } = foundry.applications.api;
class VueApplication extends VueApplicationMixin(ApplicationV2) {
static DEFAULT_OPTIONS = foundry.utils.mergeObject(super.DEFAULT_OPTIONS, {
id: `app-${Math.random().toString(36).substr(2, 9)}`,
window: {
title: `${Module.id}.title`,
icon: "fa-solid fa-triangle-exclamation"
},
position: {
width: 680,
height: "auto"
},
actions: { }
}, { inplace: false });
static PARTS = {
app: {
id: "app",
template: vueGetTemplate(`modules/${Module.id}/templates/App.vue`),
scrollable: []
}
}
}
Hooks.once('ready', async () => {
new VueApplication().render(true);
});
Basically I modified VueApplicationMixin.js PARTS template to accept a VueComponent VS a string to a path. This will let you use the Mixin with either method as its no longer trying to handle for a path or component. The major benefit for this is when I decide to release these files, people using build steps will have a smaller footprint as they wont have to package vue3-sfc-loader if they are not using it.
I still have a few things to do such as
- Handle for
RerenderingParts - Add some way to use Pinia should be optional.
- Probably other stuff I am missing.
The only problem I am having is right now, the VueApplicationMixin.mjs has one small variation and thats the first line. When using it without a build step, you have to specify where you are loading vue from such as
import { createApp } from './vue/3.4.21/vue.esm-browser.js';
Whereas when using a build step, you do
import { createApp } from 'vue';
I at first thought I could get around this by declaring animportmap but that doesn't appear to work...
@marble brook I finally had the chance to try your solution. I think I am starting to understand what you are doing here, got it working without the build, but couldn't quite figure out how to build it properly. I tried some simple Vite setups with the vue plugin.
Do you have a example of a build step you could share?
Also, idk if this is intended or not, but for me the vueGetTemplate was returning a Promise, so I changed the renderHtml function on the mixin to resolve that promise, otherwise it wouldnt render the component properly.
async _renderHTML(context, options) {
//l.log("_renderHTML", { context, options });
const rendered = {};
for (const [key, part] of Object.entries(this.constructor.PARTS)) {
if (!part) {
ui.notifications.warn(`Part "${key}" is not a supported template part for ${this.constructor.name}`);
continue;
}
part.template.then((result) => rendered[key] = result);
}
return rendered;
}
I have an updated scripts. I am working on putting together some demos and I'll be posting a GitHub using both SFC and vite as an example.
It should be up later today. I'll post in here when the GitHub and examples are ready
@blissful nebula
Alright, so here you go:
Just the files and a short rightup about what they do, I will expand on this github later: https://github.com/mouse0270/fvtt-vue
A demo using Vite to Build a Module for Foundry https://github.com/mouse0270/fvtt-vue-vite
A demo using Vue as an ESM with SFC: https://github.com/mouse0270/fvtt-vue-esm
I will work on fine tuning alot of this hopefully with feedback from others interested. Them demos are just the Grid from https://vuejs.org/examples/#grid and look like this
Had the chance to try it today, and it worked perfectly! Great demos as well.
Only small change I did was the ability to pass "props" to the Vue component from the parts object. I guess its not strictly necessary, since I could just fetch the data once the app is mounted, but still nice to have.
Thank you very much for sharing @marble brook !
I am going to update it to do props as well. I am also going to update it so that when you do app.render({parts: ["inventory"]}) it will unmount and remount the inventory part.
Just got distracted working on my UI overhaul for foundry.
@blissful nebula when you get further with your module or if you can think of anything else that will make the mixin easier to use, please let me know. I want this to be fairly easy for people plug and play and would love any feedback.
@blissful nebula could I ask how you implemented props? I seem to be confused.
nevermind, just learned you can do createApp(importedComponent, props);
Latest release to github includes
- Support for
DEFAULT_OPTIONS.actions - Window will update if
position.heightis set toauto - Reworked the Mounting system to instead create one Vue Instance with multiple components instead of Multiple Instances with on Component. Should help improve preformace if you have many windows and parts open at the same time.
- Added support for Props, and updating Props
Useful Links:
VueApplicationMixin: https://github.com/mouse0270/fvtt-vue
This github houses the required scripts to use Vue as part of ApplicationV2 using a replacement for the HandlebarsApplicationMixin. I also include a script for using vue with SFC Loader and a script I tend to expand on to offer useful helpers.
Example Module using Vite: https://github.com/mouse0270/fvtt-vue-vite
An example module using Vite as a Build Step to build your module.
Example Module using SFC: https://github.com/mouse0270/fvtt-vue-esm
This is an example module using SFC Loader instead of a build step. I do not recommend using SFC as it will be slower since the .vue files will be compiled on the clients browser. However if you have a small module, it'll probably be fine.
@royal prairie if you could kindly pin my previous post with the links, I would greatly appreciate it so that they don't get lost. I like to spam chat when I make updates.
VueApplicationMixin v0.0.5
- Added Support for
app.use()current syntax is: - Fixed missing
console.lognot checking forDEBUGand always showing.
static PARTS = {
app: {
id: "app",
component: App,
use: {
"pinia": { plugin: pinia, options: {} },
"vuetify": { plugin: vuetify }
}
}
}
I've also updated the Vite Module to Show Demos of Pina, Vueitfy and VueUse. I do not recommend using Vuetify unless maybe if you are a system developer, I was only doing it to test and make sure things work with app.use() Though part of me would love to see a vue based system using Tailwind
As always, please provide any feedback on how this should work or any suggestions of which you would like to be included.
Hey that's great! I was gonna suggest exactly a app.use() option
Maybe could also do with a app.component() at some point
This should be easy enough to incorporate. maybe something like:
static PARTS = {
app: {
id: "app",
app: App,
components: {
"CompontentA": componentA,
"CompontentB": componentB,
"CompontentC": componentC,
},
use: {
"pinia": { plugin: pinia, options: {} },
"vuetify": { plugin: vuetify }
}
}
}
Which will internally register each component via
app.component(key, value) But in a loop. so the key ComponentA becomes your globably registered component?
My main concern is I rarely find a use case of a Global Registration vs Local Registration registration of components. But adding the support should be easy enough.
I guess I could look at making it a little more interesting by supporting defineAsyncComponent
Would anyone be interesting in being able to define SHADOWROOT = true which would create and mount the vue component inside of the shadow root of .window-content
I would also need to provide a way to provide a single/multiple css styles to be included.
The reason you might want to do this is to make sure that your component is rendered into an instance where no outside styles from other modules or systems would effect your app.
Here is an example of it enabled and the For Bindings Component:
Yes I was thinking of another component library, PrimeVue, which I remembered tells you to register its components globally. But I just tested it and you can just use the local registration.
But I guess its easy enough to implement and someone might find a use for it.
Speaking of PrimeVue, would that also work the other way around? I tried using the PrimeVue styles but it ends up affecting the whole app. I gather this is the same thing you talked about not using Vuetify
If you embed it as a shadow dom as in my example above the styles would be unique to the app and shouldn't effect the whole page. Just your app.
My only concern with adding a library like primeu or beautify on top of foundry is if your module is fairly small, all you're adding a lot of overhead for. I'm probably major performance loss for something that you'd probably more easily just be able to style yourself
But let's say you were making a system and you wanted to use primeview or beautify. Then I could probably justify that heavy overhead. It also make more sense because then you could apply those styles more globally to your system than as a module
But those are really just my suggestions. You can kind of just do whatever you want. The shadow Dom mechanic would let you just embed those without affecting things outside of your app
Yes component libraries would be quite the bloat, but I think individual components wouldn't be nearly as bad. In my case I am looking for some complex charts and graphs behavior, which I imagine is kinda of a pain to do.
Yeah then when I push out the update later today that will let you encapsulate the Vue instance as part of the shadow root. It should keep styles unique to just that Vue instance. The problem is figuring out how to inject the styles in. Right now I am just accepting an array of stylesheets or a string of styles and embedding it also into the shadow root. But not sure if that's the best structure.
Also, is anyone really good with vite? I'd like to try and figure out how to setup the fvtt-vue-vite example to use hot reloading?
I am no expert but this should help: https://vitejs.dev/guide/api-hmr#hot-send-event-data
At the end there is a link to a detailed article
@marble brook have you still been working on this? Do you have a working version I could link in the community wiki?
Sort of. I had to put my dog down. But it works just might be buggy and missing documentation but I believe both examples work
I'm so sorry to hear that, my condolences
You're fine, and thank you. I was just trying to explain why I stopped working on stuff the last 3 weeks. She lived a full life, just doesnt make it easier.
Sorry about the massive delay
VueApplicationMixin v0.0.6
- Updated VueHelpers to point to
Handlebars.Helpers.XYZ - Added all Helpers from core Foundry excluding ones deprecated in v12
- Added update function that calls Instance.render to trigger foundry hooks
- Added option to attach vue instance to ShadowRoot.
just a note, I probably need to add some sort of debounce for the update, as it may trigger the handlebar functions far more often then will want it to.
This package is amazing @marble brook . Something to be aware of for users - Foundry classes tend to have private members. TIL, those do NOT play nice with Javascript proxies, which means errors if you try to pass (for ex.) a Folder or a Document as a prop or use it as a ref and then call a method/getter/setter that tries to access the private member. Easiest solution is to use toRaw() on the object when you need to access such methods.
This isn't an issue with your package or even a vue issue per se, more just something that might trip folks up at first if they aren't familiar with the Foundry API and haven't used ES6 private members before with vue.
Has anyone gotten Vue devtools to work with this? I wonder if the issue is that Vue isn't loaded yet when the page first loads (until the module opens), but the devtools extension is so flaky sometimes it's hard to know.
You can kinda cheat. Have the window open on ready and just close it. Dev tools works for me when I do this
Is this something specific to my mixin or is this an issue the HBS mixin also experiences?
I think it's a Vue (uses proxies) combined with Foundry (uses ES6 private variables) issue.
Not sure there's anything to be done about it that wouldn't be a huge amount of work. And most things still work fine, and the ones that don't can be fixed with toRaw()
I've been experimenting with porting my old version of Vue rendering from AppV1 to AppV2 for usage in document sheets. I've got a proof of concept working and managed to get <prose-mirror> editors working with some finagling as well. I'm mostly doing this as a learning exercise and so that I've got a good handle on the full rendering process for the application, but I can post a link to the repo in here for reference once I've made some more progress.
For anyone interested in using Vue for item sheets or actor sheets, I've made a lot of progress this week on building new item sheets for 13th Age using my Vue mixin. The draft PR for that is here, and I should be able to keep all of the stuff relevant to getting a working ItemSheetV2 vue-based setup in place within that single PR. Here's the link for reference: https://github.com/asacolips-projects/13th-age/pull/484
Some caveats:
- This is a different Vue mixin from what @marble brook wrote, as there were some fairly significant differences with their approach and the setup I was used to from my AppV1 vue setup from years ago. (Mouse, feel free to peruse and yoink from it liberally if anything is useful to you in there!)
- I have a
VueRenderingMixin()that supports multiple parts where each part is a component. However, it ultimately just appends those components one after the other and I'm currently only using a single part in my templates. Parts are much more useful for Handlebars rather than Vue IMO. - Documents are made available to components both as a plain
document.toObject()in the context and as the full instance viaprovide()andinject(). I just figured out the provide/inject flow this morning, so my templates don't really reflect that yet. - ProseMirror is supported, but I had to render the element markup in
_prepareContext()and then using something like<div class="prosemirror-wrapper" v-html="editor"></div>in my<ProseMirror/>component. - I also have a
BaseItemSheetV2class that I'm extending for my custom item sheet as Foundry's built-inItemSheetV2is missing a lot of essential stuff like drop handlers and image editing.
Interesting, I thought I went back and changed mine so each part was a component vs being individual instances. I will have to double check that.
As long as you don't mind, I'll take a look at it this weekend, since I should finally have some free time after like 5 months
Sure thing, my code for the 13th Age system is all MIT so you can pull from it as needed. I don't expect I'll maintain a standalone repo for Vue like you have, but if anything I've put together is helpful I'm happy to contribute back towards it.
I also have a Vite-based compilation setup for making my components available to Foundry, but the bulk of both the vue mixin and the item sheet should be able to be repurposed as needed.
I can't recall exactly how your mixin worked off the top of my head, but my approach basically does:
- Add a
_renderKey = 0prop to the application instance. - Create and mount the component(s) in
_renderFrame() - Update the component props in
_renderHTML()and also bumpthis._renderKeyby 1 so that things that struggle with reactivity (e.g. adding a new active effect or embedded document) can have re-renders forced. - Override
_replaceHTML()to do nothing since we're covered by the previous two methods.
Update the component props in _renderHTML() and also bump this._renderKey by 1 so that things that struggle with reactivity (e.g. adding a new active effect or embedded document) can have re-renders forced.
This is smart, I am stealing this... haha
I am doing this in a much worse way
.mixin({
updated() {
if (Instance.constructor.DEBUG) console.log(`VueApplicationMixin | _replaceHTML | Vue Instance Updated |`, this, Instance?.options);
// Resize the application window after the Vue Instance is updated
if (Instance?.options?.position?.height === "auto") Instance.setPosition({ height: "auto" });
// Call the render method when the Vue Instance is updated
// -- This will call FoundryVTTs Hooks related to rendering when Vue is updated
// -- Useful for when other modules listen for rendering events to inject HTML
Instance.render();
}
});
I can credit Atropos for the original idea: he recommended a similar approach when suggesting how to structure a ReactMixin for AppV2 back in February-ish, and then I mixed together that recommendation with what I did for my AppV1 solution originally.