newcat / baklavajs

Graph / node editor in the browser using VueJS
http://baklava.tech
MIT License
1.52k stars 113 forks source link

How to update SelectInterface options #355

Closed starker-xp closed 8 months ago

starker-xp commented 8 months ago

Hello, I'm looking to create a scenario editor for an RPG. I'm potentially going to end up with a lot of choices for building quests. So I'm looking for a way to fill in the SelectInterface dynamically. Or at least a way to update them.

andrews4s commented 8 months ago

Select interfaces render the items array at the point when you click it. If you want a global select source, you can define a constant at a module level and modify it as needed. If each node instance needs their own array, you can make a class based node that manages its own item array.

export class TestSelect extends AbstractNode {
    inputs: Record<string, NodeInterface<any>> = {};
    outputs: Record<string, NodeInterface<any>> = {};
    calculate?: CalculateFunction<any, any, CalculationContext<any, IEngine<any>>> | undefined;
    type: string = "TestSelect";
    items: string[];
    get title() { return "Test Select"; }
    constructor() {
        super();
        this.items = ["1"];
    }
    public onPlaced(): void {
        this.addOutput("addItem",new ButtonInterface("Add Select Item",()=>{
           this.items.push((this.items.length+1).toString()) ;
        }));
        this.addOutput("dynamicSelect",new SelectInterface("Select","1", this.items));
    }
}

It would also be nice if SelectInterface could optionally accept a function that returns an array but currently, it requires an array instance.

starker-xp commented 8 months ago

I took the problem in a different direction. I used the @vueform/multiselect package

import { type ComponentOptions, markRaw } from "vue";
import { NodeInterface } from "@baklavajs/core";
import MultiSelectInterfaceComponent from "./MultiSelectInterface.vue";

export class MultiSelectInterface<V = string> extends NodeInterface<V> {
    component = markRaw(MultiSelectInterfaceComponent) as ComponentOptions;
    baseUri: string;

    constructor(name: string, value: V, baseUri: string) {
        super(name, value);
        this.baseUri = baseUri;
    }
}

export { MultiSelectInterfaceComponent };
<script setup lang="ts">
import Multiselect from "@vueform/multiselect";
import { ref, watch, toValue } from "vue";
import type { MultiSelectInterface } from "./MultiSelectInterface";

const props = defineProps<{
    intf: MultiSelectInterface<unknown>;
}>();

const valued = ref(props.intf.value);
const el = ref<HTMLElement | null>(null);
watch(valued, () => {
    props.intf.value = toValue(valued);
});

const fetchLanguages = async (query) => {
    // From: https://www.back4app.com/database/paul-datasets/list-of-all-programming-languages/get-started/javascript/rest-api/fetch?objectClassSlug=dataset
    let where = "";
    if (query) {
        where =
            "&where=" +
            encodeURIComponent(
                JSON.stringify({
                    ProgrammingLanguage: {
                        $regex: `${query}|${query.toUpperCase()}|${query[0].toUpperCase() + query.slice(1)}`,
                    },
                }),
            );
    }

    const response = await fetch(
        "https://parseapi.back4app.com/classes/All_Programming_Languages?limit=9999&order=ProgrammingLanguage&keys=ProgrammingLanguage" +
            where,
        {
            headers: {
                "X-Parse-Application-Id": "XpRShKqJcxlqE5EQKs4bmSkozac44osKifZvLXCL", // This is the fake app's application id
                "X-Parse-Master-Key": "Mr2UIBiCImScFbbCLndBv8qPRUKwBAq27plwXVuv", // This is the fake app's readonly master key
            },
        },
    );
    const data = await response.json(); // Here you have the data that you need

    return data.results.map((item) => {
        return { value: item.ProgrammingLanguage, label: item.ProgrammingLanguage };
    });
};
</script>

<template>
    <div>
        <label class="typo__label" for="ajax">Choose a programming language</label>
        <Multiselect
            ref="el"
            v-model="valued"
            :options="
                async (query) => {
                    return await fetchLanguages(query);
                }
            "
            :filter-results="false"
            :resolve-on-load="false"
            :delay="0"
            :searchable="true"
            :object="true"
        />
    </div>
</template>
<style src="@vueform/multiselect/themes/default.css"></style>
<style>
.character-label-icon {
    margin: 0 6px 0 0;
    height: 26px;
}
.multiselect {
    color: black;
}
</style>

This is just a draft. I'll fix 2-3 problems and make a PR.