Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

toolbar #129

Open
Super-Xiao opened this issue Apr 2, 2024 · 1 comment
Open

toolbar #129

Super-Xiao opened this issue Apr 2, 2024 · 1 comment

Comments

@Super-Xiao
Copy link

请问toolbar工具栏的设置和图层按钮点击不生效需要配置什么吗

@yanzexuan1
Copy link
Collaborator

That's right, setting and layer manager are panels that need to be implemented by user. Essentially, this sdk doesn't provide any UI, it just provide APIs. User can created their own UI or plugin.

As a demo, we created our own layer manager as a plugin, I can paste the code here just for your reference:

import { DxfLayer, DxfLayers, DxfViewer, PdfLayers, Plugin, PluginConfig } from "src/core";
import { MouseButton } from "src/core/input/InputManager";

/**
 * Dxf layer manager config.
 */
export interface LayerManagerPluginConfig extends Partial<PluginConfig> {
    /**
     * Container div id.
     */
    containerId: string;
    /**
     * If panel is visible. It is visible by default.
     */
    visible?: boolean;
}

/**
 * Layer manager plugin events.
 */
type LayerManagerPluginEvents = {
    /**
     * Panel visibility change handler.
     */
    Visibilitychange: boolean;
};

/**
 * Dxf layer manager.
 * Can be used by DxfViewer.
 */
export class LayerManagerPlugin extends Plugin<LayerManagerPluginEvents> {
    public static DEFAULT_ID = "LayerManagerPlugin";
    protected cfg: LayerManagerPluginConfig;
    protected container?: HTMLDivElement;
    protected layerMgrRoot?: HTMLDivElement;
    protected layerList?: HTMLDivElement;
    protected headerText?: HTMLSpanElement;
    protected closeBtn?: HTMLSpanElement;
    protected dxfLayersArray?: (DxfLayers | PdfLayers)[];
    protected checkboxes?: HTMLInputElement[];
    protected mouseDownPositionX = -1; // -1 means invalid point
    protected mouseDownPositionY = -1;

    constructor(viewer: DxfViewer, cfg?: LayerManagerPluginConfig) {
        const id = cfg?.id || LayerManagerPlugin.DEFAULT_ID;
        super(viewer, { id });

        this.cfg = cfg || ({} as LayerManagerPluginConfig);

        if (this.cfg.visible !== false) {
            this.init();
        }

        // this.viewer.addEventListener(ViewerEvent.ModelLoaded, this.onModelLoaded);
    }

    protected init() {
        const dxfViewer = this.viewer as DxfViewer;
        if (this.cfg.containerId) {
            this.container = document.getElementById(this.cfg.containerId) as HTMLDivElement;
        }
        if (!this.container) {
            this.container = dxfViewer.widgetContainer as HTMLDivElement;
        }

        this.dxfLayersArray = dxfViewer.getLayers();
        this.buildPage();
        this.addContent();
        this.addEventHandlers();
    }

    setVisible(visible: boolean) {
        if (visible && !this.container) {
            this.init();
        }
        const div = this.layerMgrRoot as HTMLDivElement;
        const currVisible = div.style.display !== "none";
        if (currVisible === visible) {
            // do nothing if visibility is not going to be changed,
            // also to avoid dispatching unexpected event.
            return;
        }
        if (visible) {
            this.updatePage();
            div.style.display = "";
        } else {
            div.style.display = "none";
        }
        // dispatch event to inform toolbar, etc.
        this.dispatchEvent("Visibilitychange", visible);
    }

    protected show() {
        this.setVisible(true);
    }

    protected hide() {
        this.setVisible(false);
    }

    destroy(): void {
        super.destroy();

        this.closeBtn?.removeEventListener("click", this.closePanel);

        this.checkboxes?.forEach((checkbox) => {
            checkbox.removeEventListener("change", () => {
                this.checkboxHandler(checkbox);
            });
        });
        this.layerMgrRoot?.remove();
    }

    buildPage() {
        this.layerMgrRoot = document.createElement("div");
        this.layerMgrRoot.classList.add("layer-manager");

        const header = document.createElement("div");
        header.classList.add("header", "draggable");
        this.headerText = document.createElement("span");
        this.headerText.innerHTML = "Layers";
        this.headerText.classList.add("draggable");
        this.closeBtn = document.createElement("span");
        this.closeBtn.classList.add("close");
        this.closeBtn.innerHTML = "X";
        header.appendChild(this.headerText);
        header.appendChild(this.closeBtn);
        this.layerMgrRoot.appendChild(header);

        this.layerList = document.createElement("div");
        this.layerList.classList.add("layer-list");
        this.layerMgrRoot.appendChild(this.layerList);

        this.container?.appendChild(this.layerMgrRoot);
        this.updateHeaderText();
    }

    addContent() {
        let fragment = `
        <div class="layer-list-item">
            <input type="checkbox" id="toggleAllLayers" checked class="checkbox"></input>
            <span class="popup-layer-color">Color</span>
            <span class="item-layer-name">&nbsp;&nbsp;Layer name</span>
        </div>
        `;

        if (this.dxfLayersArray) {
            const shouldAppendModelId = this.dxfLayersArray.length > 1;
            for (let i = 0; i < this.dxfLayersArray.length; ++i) {
                const layers = this.dxfLayersArray[i].layers;
                let layerNames = Object.keys(layers);
                layerNames = layerNames.sort((_a, _b) => {
                    const reg = /[a-zA-Z0-9]/;
                    // Only commpare strings, comparing numbers make them converting to numbers, thus become NaN
                    const a = _a.toString();
                    const b = _b.toString();
                    // The reason to compare the first elements is, there might be both Chinese and English
                    // in a string, simply sort with the first element is ok for such case.
                    if (reg.test(a[0]) || reg.test(b[0])) {
                        if (a > b) {
                            return 1;
                        } else if (a < b) {
                            return -1;
                        } else {
                            return 0;
                        }
                    } else {
                        return a.localeCompare(b);
                    }
                });
                for (const layerName of layerNames) {
                    const dxfLayer = layers[layerName];
                    // add "<modelId>" as layer name prefix when there is more than one models
                    const tmpLayerName = shouldAppendModelId
                        ? `&lt;${this.dxfLayersArray[i].modelId}&gt; ${layerName}`
                        : layerName;
                    const color = this.convertDecimalToHex((dxfLayer as DxfLayer).color);
                    const listItem = this.generateListItem(tmpLayerName, dxfLayer.visible, color);
                    fragment += listItem;
                }
            }
        }

        if (this.layerList) {
            this.layerList.innerHTML = fragment;
        }

        // add checkboxes events
        const checkboxs = this.layerMgrRoot?.querySelectorAll("input[type=checkbox]");
        this.checkboxes = [].slice.call(checkboxs, 0);

        this.checkboxes.forEach((checkbox) => {
            checkbox.addEventListener("change", () => {
                this.checkboxHandler(checkbox);
            });
        });
    }

    generateListItem(layer: string, visible: boolean, color?: string) {
        const itemColorStyle = color ? `background-color: ${color}` : "opacity: 0.1";
        const listItem = `
            <div class="layer-list-item">
                <input type="checkbox" value="${layer}" ${visible ? "checked" : ""} class="checkbox">
                <div class="item-color" style="${itemColorStyle}"></div>
                <span class="item-layer-name" title="${layer}">${layer}</span>
            </div>
            `;

        return listItem;
    }

    closePanel() {
        this.hide();
        // TODO: inform toolbar to update button status
        // eslint-disable-next-line @typescript-eslint/no-explicit-any
        // (this.viewer as any).toolbar.setActive("Layers", false);
    }

    addEventHandlers() {
        this.closeBtn?.addEventListener("click", this.closePanel.bind(this));

        this.layerMgrRoot?.addEventListener("pointerdown", this.onPointerDown);
    }

    protected onPointerDown = (e: MouseEvent) => {
        const t = e.target as HTMLDivElement;
        if (t?.classList.contains("draggable") && e.button === MouseButton.Left) {
            this.mouseDownPositionX = e.x;
            this.mouseDownPositionY = e.y;
            document.addEventListener("pointermove", this.onPointerMove);
            document.addEventListener("pointerup", this.onPointerUp);
        }
    };

    protected onPointerMove = (e: MouseEvent) => {
        const panel = this.layerMgrRoot;
        const viewerContainer = this.viewer.viewerContainer;
        if (!panel || !viewerContainer) {
            return;
        }
        const viewerRight = viewerContainer.clientLeft + viewerContainer.clientWidth;
        const viewerBottom = viewerContainer.clientTop + viewerContainer.clientHeight;
        const deltaX = e.x - this.mouseDownPositionX;
        const deltaY = e.y - this.mouseDownPositionY;
        let targetX = panel.offsetLeft + deltaX;
        let targetY = panel.offsetTop + deltaY;
        if (targetX < 0) {
            targetX = 0; // panel left exceeds boundary
        }
        if (targetX > 0 && targetX + panel.clientWidth > viewerRight) {
            // deltaY > 0 means move right, only allow to move left in this case.
            if (deltaX > 0) {
                targetX = panel.offsetLeft; // panel right exceeds boundary
            }
        }
        if (targetY < 0) {
            targetY = 0; // panel top exceeds boundary
        }
        if (targetY > 0 && targetY + panel.clientHeight > viewerBottom) {
            // deltaY > 0 means move down, only allow to move up in this case.
            if (deltaY > 0) {
                targetY = panel.offsetTop; // panel bottom exceeds boundary
            }
        }
        panel.style.left = `${targetX}px`;
        panel.style.top = `${targetY}px`;
        this.mouseDownPositionX = e.x;
        this.mouseDownPositionY = e.y;
    };

    protected onPointerUp = () => {
        document.removeEventListener("pointermove", this.onPointerMove);
        document.removeEventListener("pointerup", this.onPointerUp);
    };

    checkboxHandler(checkbox: HTMLInputElement) {
        if (!this.dxfLayersArray || !this.checkboxes) {
            return;
        }
        const dxfViewer = this.viewer as DxfViewer;
        if (checkbox.id === "toggleAllLayers") {
            for (let i = 0; i < this.dxfLayersArray.length; ++i) {
                const modelId = this.dxfLayersArray[i].modelId;
                const layers = this.dxfLayersArray[i].layers;
                Object.keys(layers).forEach((layerName) => {
                    dxfViewer.setLayerVisibility(layerName, checkbox.checked, modelId);
                });
            }

            this.checkboxes?.forEach((cb) => (cb.checked = checkbox.checked));
            return;
        }

        let modelId = "";
        let layerName = checkbox.value;
        const idx = layerName.indexOf(">");
        if (idx !== -1) {
            modelId = layerName.slice(1, idx);
            layerName = layerName.slice(idx + 2);
        }
        if (!modelId) {
            modelId = this.dxfLayersArray[0].modelId;
        }
        dxfViewer.setLayerVisibility(layerName, checkbox.checked, modelId);
    }

    updatePage() {
        if (!this.dxfLayersArray) {
            return;
        }
        const dxfViewer = this.viewer as DxfViewer;
        const layers = dxfViewer.getLayers();
        // if layers.length is the same, there is no change to loaded models
        if (layers.length === this.dxfLayersArray.length) {
            return;
        }

        this.dxfLayersArray = layers;
        this.addContent();
        this.updateHeaderText();
    }

    updateHeaderText() {
        if (!this.dxfLayersArray || !this.headerText) {
            return;
        }
        let layerCount = 0;
        for (let i = 0; i < this.dxfLayersArray.length; ++i) {
            const layers = this.dxfLayersArray[i].layers;
            layerCount += Object.keys(layers).length;
        }
        this.headerText.innerHTML = `Layers (count: ${layerCount})`;
    }

    convertDecimalToHex(decimal: number): string | undefined {
        if (decimal === undefined) {
            return undefined;
        }
        const hex = decimal.toString(16);
        return `#${hex.padStart(6, "0")}`;
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants