Source: components/SelectionInvert.js

/**
 * @module components/SelectionInvert.js
 * @name SelectionInvert
 * @copyright 2023 3Liz
 * @author BOISTEAULT Nicolas
 * @license MPL-2.0
 */
import {mainLizmap, mainEventDispatcher} from '../modules/Globals.js';
import {html, render} from 'lit-html';

import '../images/svg/mActionInvertSelection.svg';

/**
 * Web component used to invert selection on layer selection defined by 'feature-type' attribute
 * or allFeatureTypeSelected defined in SelectionTool module
 * @class
 * @name SelectionInvert
 * @augments HTMLElement
 */
export default class SelectionInvert extends HTMLElement {
    /**
     * The HTML element constructor
     * @class
     * @private
     */
    constructor() {
        super();
        this._featureType = null;
        this._layerName = null;
        this._isDisabled = false;
        this._isHidden = false;
        this._updateProperties();
    }

    /**
     * Update the element state : hidden and disabled
     * @private
     */
    _updateState() {
        if (this.hasAttribute('feature-type')) {
            this._isDisabled = true;
            this._isHidden = true;
            // check if layer has selected features
            if (this._layerName in mainLizmap.config.layers) {
                const layerConfig = mainLizmap.config.layers[this._layerName];
                // update template parameters if layer has selected features
                if (layerConfig && 'selectedFeatures' in layerConfig) {
                    const selectedFeatures = layerConfig['selectedFeatures'];
                    if (selectedFeatures && selectedFeatures.length) {
                        this._isDisabled = false;
                        this._isHidden = false;
                    }
                }
            }
        } else {
            // update template parameters if selection tool has selected features
            this._isDisabled =
                mainLizmap.selectionTool.selectedFeaturesCount === 0 ||
                mainLizmap.selectionTool.allFeatureTypeSelected.length > 1;
            this._isHidden = false;
        }
    }

    /**
     * Update the element properties : feature type and layer name
     * Then update the element state.
     * @private
     */
    _updateProperties() {
        this._title =  lizDict['selectiontool.toolbar.action.invert'];
        this._featureType = null;
        this._layerName = null;
        if (this.hasAttribute('feature-type')) {
            this._featureType = this.getAttribute('feature-type');
            this._layerName = mainLizmap.getLayerNameByCleanName(this._featureType);
        }
        this._updateState();
    }

    /**
     * Invoked when a component is added to the document's DOM.
     */
    connectedCallback() {

        const mainTemplate = (hiddenClass, isDisabled, title, clickHandler) => html`
        <button
            type="button"
            class="selectiontool-invert btn btn-sm ${hiddenClass}"
            data-bs-toggle="tooltip"
            data-bs-title="${title}"
            ?disabled=${isDisabled}
            @click=${clickHandler}
            >
            <svg class="icon-">
                <use xlink:href="#mActionInvertSelection"></use>
            </svg>
        </button>`;

        // render the template with the parameters
        render(
            mainTemplate(
                this.hidden ? 'hide' : '',
                this.disabled,
                this.title,
                this.click
            ),
            this
        );

        // Add tooltip on buttons
        // TODO allow tooltip on disabled buttons : https://stackoverflow.com/a/19938049/2000654
        $('button', this).tooltip({
            placement: this.hasAttribute('tooltip-placement') ? this.getAttribute('tooltip-placement') : 'top'
        });

        mainEventDispatcher.addListener(
            () => {
                // update state due to selection change
                this._updateState();

                // render the template with the parameters
                render(
                    mainTemplate(
                        this.hidden ? 'hide' : '',
                        this.disabled,
                        this.title,
                        this.click
                    ),
                    this
                );
            },
            ['selectionTool.allFeatureTypeSelected', 'selection.changed']
        );
    }

    /**
     * Invoked when a component is removed from the document's DOM.
     */
    disconnectedCallback() {
    }

    /**
     * Invoked when one of the element’s observedAttributes changes.
     * @param {string} name - The name of the attribute that changed.
     * @param {string} oldValue - The old value of the attribute.
     * @param {string} newValue - The new value of the attribute.
     */
    attributeChangedCallback(name, oldValue, newValue) {
        if (oldValue === newValue) {
            return;
        }
        // Listen to the change of the updated attribute
        if (name === 'feature-type') {
            this._updateProperties();
        }
    }

    /**
     * An array containing the names of all attributes for which the
     * element needs change notifications.
     * @type {string[]}
     */
    static get observedAttributes() { return ['feature-type']; }

    /**
     * The title of the button.
     * @type {string}
     */
    get title() {
        return this._title;
    }

    /**
     * The layer name associated to the feature-type attribute.
     * @type {null|string}
     */
    get layerName() {
        return this._layerName;
    }

    /**
     * The disabled state of the button.
     * @type {boolean}
     */
    get disabled() {
        return this._isDisabled;
    }

    /**
     * The hidden state of the button.
     * @type {boolean}
     */
    get hidden() {
        return this._isHidden;
    }

    /**
     * Click action
     * @returns {void}
     */
    click() {
        // Invert selection if not disabled
        if (this.disabled) {
            return;
        }
        // If no feature-type attribute, use the first feature type selected
        // to invert selection
        let layerName = this.layerName;
        if (!layerName &&
            mainLizmap.selectionTool.allFeatureTypeSelected.length > 1) {
            layerName = mainLizmap.selectionTool.allFeatureTypeSelected[0];
        }
        // Invert selection if layer name is defined
        if (layerName) {
            mainLizmap.selectionTool.invert(layerName);
        }
    }
}