#AppV2 Vue

1 messages · Page 1 of 1 (latest)

robust bough
blissful nebula
#

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.

marble brook
#

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.

blissful nebula
#

For the document class itself, did you do something different? I was particularly confused about HandlebarsApplicationMixins

marble brook
#

I wrote my own ApplicationMixin called VueApplicationMixin that basically injects the Vue component in as a part

marble brook
#

@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.

marble brook
#

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 Rerendering Parts
  • 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...

blissful nebula
#

@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;
}
marble brook
#

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

marble brook
#

@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

blissful nebula
#

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 !

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.

marble brook
#

@blissful nebula could I ask how you implemented props? I seem to be confused.

#

nevermind, just learned you can do createApp(importedComponent, props);

marble brook
#

Latest release to github includes

  • Support for DEFAULT_OPTIONS.actions
  • Window will update if position.height is set to auto
  • 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
marble brook
#

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.

marble brook
#

VueApplicationMixin v0.0.5

  • Added Support for app.use() current syntax is:
  • Fixed missing console.log not checking for DEBUG and 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.

blissful nebula
#

Hey that's great! I was gonna suggest exactly a app.use() option

#

Maybe could also do with a app.component() at some point

marble brook
# blissful nebula 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

marble brook
#

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:

blissful nebula
blissful nebula
marble brook
#

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

blissful nebula
#

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.

marble brook
#

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.

marble brook
#

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?

blissful nebula
#

At the end there is a link to a detailed article

rain iron
#

@marble brook have you still been working on this? Do you have a working version I could link in the community wiki?

marble brook
#

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

rain iron
#

I'm so sorry to hear that, my condolences

marble brook
#

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.

marble brook
#

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.
marble brook
#

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.

pliant estuary
#

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.

pliant estuary
#

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.

marble brook
marble brook
pliant estuary
#

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()

oak stone
#

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.

oak stone
#

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 via provide() and inject(). 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 BaseItemSheetV2 class that I'm extending for my custom item sheet as Foundry's built-in ItemSheetV2 is missing a lot of essential stuff like drop handlers and image editing.
marble brook
#

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

oak stone
#

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 = 0 prop to the application instance.
  • Create and mount the component(s) in _renderFrame()
  • 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.
  • Override _replaceHTML() to do nothing since we're covered by the previous two methods.
marble brook
#

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();
    }
});
oak stone
#

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.