cds-astro / aladin-lite

An astronomical HiPS visualizer in the browser
https://aladin.cds.unistra.fr/AladinLite/
GNU General Public License v3.0
103 stars 42 forks source link

expose an API for creating UI elements #188

Open havok2063 opened 3 months ago

havok2063 commented 3 months ago

Is it possible to expose an API to create more complex custom UI elements? For example, I'd to create a button with a context menu, similar to the current Settings or Overlays button, with my own content. I'd also like to re-use some of your existing elements, like the color picker, or visibility, and remove buttons.

What is the best way to create more complex custom UI elements?

Or alternatively, is there an easy way to modify Aladin's existing elements? For example, adding a color picker option to a new Catalog layer, under the Overlays menu, to interactively change the source color?

bmatthieu3 commented 3 months ago

It is not yet finished but here is a working example (with the latest) for setting a custom catalog setting ui to change its color:


<div id="aladin-lite-div" style="width: 1024px; height: 768px">
    <div class="box-content" style="width:200px">
        <div style="display:inline-flex">
            <label for="cat-color">Color</label>
            <input type="color" id="cat-color" value="#ff0000">
        </div>

    </div>
</div>

<script type="module">
    import A from '../src/js/A.js';
    let aladin;
    A.init.then(() => {
        var aladin = A.aladin(
            '#aladin-lite-div',
            {
                fov: 1.5, // initial field of view in degrees
                target: 'NGC 2175', // initial target
            }
        );

        // Add a catalog
        let cat = A.catalogFromSimbad('NGC 2175', 0.1, {onClick: 'showTable'});
        aladin.addCatalog(cat);

        // Logic for changing the color of catalog sources
        let colorPicker = document.querySelector('#cat-color');
        colorPicker.value = cat.color;
        colorPicker.addEventListener('input', function (e) {
            // Change the color of the catalog
            cat.updateShape({color: this.value});
        })

        // Define the box
        let catalogSettingsBox = A.box({
            header: {
                title: "Settings",
            },
            content: document.querySelectorAll('.box-content')[0],
        });
        catalogSettingsBox._hide();

        // Define the button that toggles the box
        let catalogSettingsBtn = A.button({
            content: 'Catalog',
            classList: ['catalogSettingsTogglerBtn'],
            action(o) {
                if (catalogSettingsBox.isHidden) {
                    catalogSettingsBox._show({
                        position: {
                            nextTo: catalogSettingsBtn,
                            direction: 'right',
                        }
                    })
                } else {
                    catalogSettingsBox._hide()
                }
            }
        });

        aladin.addUI(catalogSettingsBtn)
        aladin.addUI(catalogSettingsBox)
    });
</script>
<style>
    .catalogSettingsTogglerBtn {
        position: absolute;
        top: 200px;
        left: 0;
    }
</style>

I am not sure how to make the API better. I would tend to say user do not have to define the Box javascript object. Maybe, the user could add a specific class name (like aladin-box) to the div of a box which would be retrieved by aladin internally and Box objects could be created with the content. The user could then have all the boxes from the aladin object and could decide which he wants to show or hide (like a toggler button).

Would it be sufficient for your use case ?

havok2063 commented 2 months ago

Thanks. I haven't been able to quite get this to work, but I haven't tested it against the latest main repo code. I tried adapting it in the live playground here, https://aladin.cds.unistra.fr/AladinLite/doc/API/examples/marker-creation/, but the "Catalog" button does not open anything. But I'll try testing it again.

For expanding the API, I am imagining exposing all the current UI elements you use onto the A object, that lets the user combine different existing UI elements in new ways, e.g. A.colorBtn, or A.removeBtn. For example, to create a new button that when clicked, displays a stack of buttons with two rows: 1st row, a color picker button; 2nd row, a range slider button and a remove button.

        let myStack = A.stackBox({
           components: [A.colorBtn(), [A.sliderBtn(), A.removeBtn()] ] 
})

        let newBtn = A.button({
            content: 'Catalog',
            classList: ['newBtn'],
            id: 'newCatalogBtn',
            ctxMenu:  myStack,
        });
       aladin.addUI(newBtn)

Instantiating bare , e.g. A.colorBtn() would give you a default button, but it could also take options for adding event handlers or other config settings. I also think adding unique div ids to each element would be helpful for selection. To add a new color button to one specific Catalog Overlay layer, I might do something like

A.select('#catalog1Overlay').add(A.colorBtn, 0) 

which would add a new color picker button to the Catalog 1 Overlay before the Visibility button. This would also make it easier to assign existing elements to new button functionality.