Home Reference Source

cables_dev/cables_ui/src/ui/components/opparampanel/op_parampanel.js

import { Logger, ele, Events } from "cables-shared-client";
import { getHandleBarHtml } from "../../utils/handlebars.js";
import text from "../../text.js";
import { PortHtmlGenerator } from "./op_params_htmlgen.js";
import ParamsListener from "./params_listener.js";
import userSettings from "../usersettings.js";
import gluiconfig from "../../glpatch/gluiconfig.js";

/**
 * op parameter panel
 *
 * @class OpParampanel
 * @extends {Events}
 */
class OpParampanel extends Events
{
    constructor(eleid)
    {
        super();

        this.panelId = CABLES.simpleId();
        this._eleId = eleid;
        this._log = new Logger("OpParampanel");
        this._htmlGen = new PortHtmlGenerator(this.panelId);

        this._currentOp = null;
        this._eventPrefix = CABLES.shortId();
        this._isPortLineDragDown = false;

        this._portsIn = [];
        this._portsOut = [];

        this._paramsListener = new ParamsListener(this.panelId);

        this._portUiAttrListeners = [];
        this._startedGlobalListeners = false;

        this.reloadListener = null;
    }

    get op()
    {
        return this._currentOp;
    }

    setParentElementId(eleid)
    {
        this._eleId = eleid;
    }

    dispose()
    {
        this._stopListeners();
        this._watchPorts.length = 0;
    }

    clear()
    {
        this._stopListeners();
        this._currentOp = null;
    }

    refresh()
    {
        this.show(this._currentOp);
    }


    _onUiAttrChangeOp(attr)
    {
        if (attr.hasOwnProperty("uierrors")) this.updateUiErrors();
    }

    _onUiAttrChangePort(attr, port)
    {
        if (!attr) return;
        if (attr.hasOwnProperty("greyout")) this.refreshDelayed();

        // todo: only update this part of the html
    }

    _stopListeners(op)
    {
        op = op || this._currentOp;
        if (!op) return;

        for (let i = 0; i < this._portUiAttrListeners.length; i++)
        {
            const listener = this._portUiAttrListeners[i];
            listener.port.off(listener.listenId);
        }
        this._portUiAttrListeners.length = 0;
        this.onOpUiAttrChange = op.off(this.onOpUiAttrChange);
    }

    _startListeners(op)
    {
        if (!op)
        {
            this._stopListeners();
            return;
        }

        if (!this.hasExposeListener)
        {
            this.hasExposeListener = gui.corePatch().on("subpatchExpose",
                (subpatchid) =>
                {
                    if (
                        op &&
                        op.storage && op.storage.subPatchVer &&
                        op.patchId.get() === subpatchid
                    )
                    {
                        op.refreshParams();
                    }
                });
        }

        this.onOpUiAttrChange = op.on("onUiAttribsChange", this._onUiAttrChangeOp.bind(this));

        for (let i = 0; i < this._portsIn.length; i++)
        {
            const listenId = this._portsIn[i].on(
                "onUiAttrChange",
                this._onUiAttrChangePort.bind(this),
                this._eventPrefix);
            this._portUiAttrListeners.push({ "listenId": listenId, "port": this._portsIn[i] });
        }
    }


    refreshDelayed()
    {
        clearTimeout(this.refreshTimeout);
        this.refreshTimeout = setTimeout(() =>
        {
            this.show(this._currentOp);
        }, 50);
    }

    show(op)
    {
        if (!CABLES.UI.loaded) return;
        if (!this._startedGlobalListeners)
        {
            this._startedGlobalListeners = true;

            gui.corePatch().on("bookmarkschanged", () => { gui.bookmarks.needRefreshSubs = true; this._startedGlobalListeners = true; if (!this._currentOp) gui.patchParamPanel.show(true); });
            gui.corePatch().on("subpatchesChanged", () => { gui.bookmarks.needRefreshSubs = true; this._startedGlobalListeners = true; if (!this._currentOp) gui.patchParamPanel.show(true); });
            gui.corePatch().on("subpatchCreated", () => { gui.bookmarks.needRefreshSubs = true; this._startedGlobalListeners = true; if (!this._currentOp) gui.patchParamPanel.show(true); });
            gui.corePatch().on("patchLoadEnd", () => { gui.bookmarks.needRefreshSubs = true; this._startedGlobalListeners = true; if (!this._currentOp) gui.patchParamPanel.show(true); });
        }

        if (this.reloadListener)
            this.reloadListener = gui.on("opReloaded", () =>
            {
                this.refreshDelayed();
            });

        const perf = CABLES.UI.uiProfiler.start("[opparampanel] show");


        if (typeof op == "string") op = gui.corePatch().getOpById(op);

        if (!gui.showingtwoMetaPanel && gui.metaTabs.getActiveTab() && gui.metaTabs.getActiveTab().title != "op")
            gui.metaTabs.activateTabByName("op");


        if (this._currentOp) this._stopListeners();

        this._currentOp = op;


        if (!op)
        {
            return;
        }

        this._portsIn = op.portsIn;
        this._portsOut = op.portsOut;

        if (op.storage && op.storage.subPatchVer)
        {
            const ports = gui.patchView.getSubPatchExposedPorts(op.patchId.get());
            for (let i = 0; i < ports.length; i++)
            {
                if (ports[i].direction === CABLES.PORT_DIR_IN && this._portsIn.indexOf(ports[i]) == -1) this._portsIn.push(ports[i]);
                if (ports[i].direction === CABLES.PORT_DIR_OUT && this._portsOut.indexOf(ports[i]) == -1) this._portsOut.push(ports[i]);
            }
        }

        this._startListeners(this._currentOp);

        op.emitEvent("uiParamPanel", op);

        const perfHtml = CABLES.UI.uiProfiler.start("[opparampanel] build html ");

        gui.opHistory.push(op.id);
        gui.setTransformGizmo(null);




        this.emitEvent("opSelected", op);

        op.isServerOp = gui.serverOps.isServerOp(op.objName);

        // show first anim in timeline
        // if (self.timeLine)
        // {
        //     let foundAnim = false;
        //     for (let i = 0; i < this._portsIn.length; i++)
        //     {
        //         if (this._portsIn[i].isAnimated())
        //         {
        //             self.timeLine.setAnim(this._portsIn[i].anim, {
        //                 "name": this._portsIn[i].name,
        //             });
        //             foundAnim = true;
        //             continue;
        //         }
        //     }
        //     if (!foundAnim) self.timeLine.setAnim(null);
        // }


        this._portsIn.sort(function (a, b) { return (a.uiAttribs.order || 0) - (b.uiAttribs.order || 0); });


        let html = this._htmlGen.getHtmlOpHeader(op);

        gui.showInfo(text.patchSelectedOp);

        if (this._portsIn.length > 0)
        {
            const perfLoop = CABLES.UI.uiProfiler.start("[opparampanel] _showOpParamsLOOP IN");
            html += this._htmlGen.getHtmlHeaderPorts("in", "Input");
            html += this._htmlGen.getHtmlInputPorts(this._portsIn);

            perfLoop.finish();
        }

        if (this._portsOut.length > 0)
        {
            html += this._htmlGen.getHtmlHeaderPorts("out", "Output");

            const perfLoopOut = CABLES.UI.uiProfiler.start("[opparampanel] _showOpParamsLOOP OUT");

            html += this._htmlGen.getHtmlOutputPorts(this._portsOut);

            perfLoopOut.finish();
        }

        html += getHandleBarHtml("params_op_foot", { "op": op, "showDevInfos": userSettings.get("devinfos") });

        const el = document.getElementById(this._eleId || gui.getParamPanelEleId());

        if (el) el.innerHTML = html;
        else return;

        this._paramsListener.init({ "op": op });

        perfHtml.finish();

        this.updateUiAttribs();

        for (let i = 0; i < this._portsIn.length; i++)
        {
            if (this._portsIn[i].uiAttribs.display && this._portsIn[i].uiAttribs.display == "file")
            {
                let shortName = String(this._portsIn[i].get() || "none");
                if (shortName.indexOf("/") > -1) shortName = shortName.substr(shortName.lastIndexOf("/") + 1);

                if (ele.byId("portFilename_" + i))
                    ele.byId("portFilename_" + i).innerHTML = "<span class=\"button-small \" style=\"text-transform:none;\"><span class=\"icon icon-file\"></span>" + shortName + "</span>";

                let srcEle = ele.byId("portFilename_" + i + "_src");
                if (srcEle)
                {
                    let src = "";
                    let fn = this._portsIn[i].get() || "";

                    if (fn == "" || fn == 0)src = "";
                    else if (!fn.startsWith("/")) src = "ext";

                    if (fn.startsWith("file:")) src = "file";
                    if (fn.startsWith("data:")) src = "dataUrl";

                    if (fn.startsWith("http://") || fn.startsWith("https://"))
                    {
                        const parts = fn.split("/");
                        if (parts && parts.length > 1) src = "ext: " + parts[2];
                    }
                    if (fn.startsWith("/assets/" + gui.project()._id)) src = "this patch";
                    if (fn.startsWith("/assets/") && !fn.startsWith("/assets/" + gui.project()._id))
                    {
                        const parts = fn.split("/");
                        if (parts && parts.length > 1) src = "<a target=\"_blank\" class=\"link\" href=\"" + CABLES.platform.getCablesUrl() + "/edit/" + parts[2] + "\">other patch</a>";
                    }
                    if (fn.startsWith("/assets/library/")) src = "lib";


                    if (src != "") src = "[ " + src + " ]";

                    srcEle.innerHTML = src;
                }
            }

            const f = (e) =>
            {
                if (!this._isPortLineDragDown) return;

                if (gui.patchView._patchRenderer.getOp)
                {
                    const glOp = gui.patchView._patchRenderer.getOp(op.id);

                    if (glOp && this._portsIn[i])
                    {
                        const glPort = glOp.getGlPort(this._portsIn[i].name);

                        if (this._portsIn[i].name == this._portLineDraggedName)
                            gui.patchView._patchRenderer.emitEvent("mouseDownOverPort", glPort, glOp.id, this._portsIn[i].name, e);
                    }
                }
            };

            document.getElementById("portLineTitle_in_" + i).addEventListener("pointerup", () => { this._isPortLineDragDown = false; this._portLineDraggedName = null; }, { "passive": false });
            document.getElementById("portLineTitle_in_" + i).addEventListener("pointerdown", (e) => { this._isPortLineDragDown = true; this._portLineDraggedName = e.target.dataset.portname; }, { "passive": false });
            if (document.getElementById("patchviews")) document.getElementById("patchviews").addEventListener("pointerenter", f);
        }




        for (const ipo in this._portsOut)
        {
            this._showOpParamsCbPortDelete(ipo, op);
            (function (index)
            {
                const elem = ele.byId("portTitle_out_" + index);
                if (elem)elem.addEventListener("click", (e) =>
                {
                    const p = this._portsOut[index];
                    if (!p.uiAttribs.hidePort)
                        gui.opSelect().show({ "x": p.parent.uiAttribs.translate.x + index * (gluiconfig.portWidth + gluiconfig.portPadding), "y": p.op.uiAttribs.translate.y + 50, }, op, p);
                }, { "passive": false });
                else this._log.warn("ele not found: portTitle_out_" + index);
            }.bind(this)(ipo));

            document.getElementById("portLineTitle_out_" + ipo).addEventListener("pointerup", () => { this._isPortLineDragDown = false; this._portLineDraggedName = null; }, { "passive": false });
            document.getElementById("portLineTitle_out_" + ipo).addEventListener("pointerdown", (e) => { this._isPortLineDragDown = true; this._portLineDraggedName = e.target.dataset.portname; }, { "passive": false });

            if (document.getElementById("patchviews")) document.getElementById("patchviews").addEventListener("pointerenter", (e) =>
            {
                if (!this._isPortLineDragDown) return;
                if (gui.patchView._patchRenderer.getOp)
                {
                    const glOp = gui.patchView._patchRenderer.getOp(op.id);
                    if (glOp && this._portsOut[ipo])
                    {
                        const glPort = glOp.getGlPort(this._portsOut[ipo].name);
                        if (this._portsOut[ipo].name == this._portLineDraggedName)
                            gui.patchView._patchRenderer.emitEvent("mouseDownOverPort", glPort, glOp.id, this._portsOut[ipo].name, e);
                    }
                }
            }, { "passive": false });
        }


        ele.forEachClass("portCopyClipboard", (ell) =>
        {
            ell.addEventListener("click", (e) =>
            {
                if (!navigator.clipboard) return;

                const cop = gui.corePatch().getOpById(e.target.dataset.opid);
                const port = cop.getPortByName(e.target.dataset.portname);

                navigator.clipboard
                    .writeText(String(port.get()))
                    .then(() =>
                    {
                        CABLES.UI.notify("Copied value to clipboard");
                    })
                    .catch((err) =>
                    {
                        console.warn("copy to clipboard failed", err);
                    });

                e.preventDefault();
            }, { "passive": false });
        });


        perf.finish();
    }

    updateUiErrors()
    {
        if (!this._currentOp) return;
        const el = document.getElementById("op_params_uierrors");

        if (!this._currentOp.uiAttribs.uierrors || this._currentOp.uiAttribs.uierrors.length == 0)
        {
            if (el)el.innerHTML = "";
            return;
        }
        else
        if (document.getElementsByClassName("warning-error") != this._currentOp.uiAttribs.uierrors.length)
        {
            if (el)el.innerHTML = "";
        }

        if (!el)
        {
            this._log.warn("no uiErrors html ele?!");
        }
        else
        {
            for (let i = 0; i < this._currentOp.uiAttribs.uierrors.length; i++)
            {
                const err = this._currentOp.uiAttribs.uierrors[i];

                let div = document.getElementById("uierror_" + err.id);

                let str = "";
                if (err.level == 0) str += "<b>Hint: </b>";
                if (err.level == 1) str += "<b>Warning: </b>";
                if (err.level == 2) str += "<b>Error: </b>";
                str += err.txt;

                if (!div)
                {
                    div = document.createElement("div");
                    div.id = "uierror_" + err.id;
                    div.classList.add("warning-error");
                    if (CABLES.UTILS.isNumeric(err.level))
                        div.classList.add("warning-error-level" + err.level);
                    else
                    {
                        console.error("err level not numeric", err.level);
                        console.log((new Error().stack));
                    }

                    el.appendChild(div);
                }

                div.innerHTML = str;
            }
            gui.patchView.checkPatchErrors();
        }
    }

    updateUiAttribs()
    {
        if (gui.patchView.isPasting) return;
        if (!this._currentOp) return;

        this._uiAttrFpsLast = this._uiAttrFpsLast || performance.now();
        this._uiAttrFpsCount++;

        if (performance.now() - this._uiAttrFpsLast > 1000)
        {
            this._uiAttrFpsLast = performance.now();
            if (this._uiAttrFpsCount >= 10) this._log.log("many ui attr updates! ", this._uiAttrFpsCount, this._currentOp.name);
            this._uiAttrFpsCount = 0;
        }

        const perf = CABLES.UI.uiProfiler.start("[opparampanel] updateUiAttribs");
        let el = null;

        el = document.getElementById("options_warning");
        if (el)
        {
            if (!this._currentOp.uiAttribs.warning || this._currentOp.uiAttribs.warning.length === 0) el.style.display = "none";
            else
            {
                el.style.display = "block";
                if (el) el.innerHTML = this._currentOp.uiAttribs.warning;
            }
        }

        el = document.getElementById("options_hint");
        if (el)
        {
            if (!this._currentOp.uiAttribs.hint || this._currentOp.uiAttribs.hint.length === 0) el.style.display = "none";
            else
            {
                el.style.display = "block";
                if (el) el.innerHTML = this._currentOp.uiAttribs.hint;
            }
        }

        el = document.getElementById("options_error");
        if (el)
        {
            if (!this._currentOp.uiAttribs.error || this._currentOp.uiAttribs.error.length === 0) el.style.display = "none";
            else
            {
                el.style.display = "block";
                if (el) el.innerHTML = this._currentOp.uiAttribs.error;
            }
        }

        el = document.getElementById("options_info");
        if (el)
        {
            if (!this._currentOp.uiAttribs.info) el.style.display = "none";
            else
            {
                el.style.display = "block";
                el.innerHTML = "<div class=\"panelhead\">info</div><div class=\"panel\">" + this._currentOp.uiAttribs.info + "</div>";
            }
        }

        this.updateUiErrors();

        perf.finish();
    }

    _showOpParamsCbPortDelete(index, op)
    {
        const el = ele.byId("portdelete_out_" + index);
        if (el)el.addEventListener("click", (e) =>
        {
            this._portsOut[index].removeLinks();
            this.show(op);
        });
    }


    setCurrentOpComment(v)
    {
        if (this._currentOp)
        {
            this._currentOp.uiAttr({ "comment": v });
            if (v.length == 0) this._currentOp.uiAttr({ "comment": null });
            this._currentOp.patch.emitEvent("commentChanged");
            // gui.setStateUnsaved({ "op": this._currentOp });
            gui.savedState.setUnSaved("op comment", this._currentOp.uiAttribs.subPatch);
        }
        else
        {
            this._log.warn("no current op comment");
        }
    }

    setCurrentOpTitle(t)
    {
        if (this._currentOp) this._currentOp.setTitle(t);

        // if (defaultops.isSubPatchOpName(this._currentOp.objName))
        if (this._currentOp && this._currentOp.storage && this._currentOp.storage.subPatchVer)
        {
            this._currentOp.patch.emitEvent("subpatchesChanged");
        }
    }

    isCurrentOp(op)
    {
        return this._currentOp == op;
    }

    isCurrentOpId(opid)
    {
        if (!this._currentOp) return false;
        return this._currentOp.id == opid;
    }

    // OLD SUBPATCH LIST!!!!!! REMOVE
    subPatchContextMenu(el)
    {
        const outer = gui.patchView.getSubPatchOuterOp(el.dataset.id);

        const items = [];
        if (outer && outer.storage && outer.storage.blueprint)
        {
            items.push({
                "title": "Goto Blueprint Op",
                func()
                {
                    gui.patchView.focusSubpatchOp(el.dataset.id);
                },
            });
            items.push({
                "title": "Update Blueprint",
                func()
                {
                    const bp = gui.patchView.getBlueprintOpFromBlueprintSubpatchId(el.dataset.id);
                    if (bp) gui.patchView.updateBlueprints([bp]);
                },
            });
            items.push({
                "title": "Open Patch",
                "iconClass": "icon icon-external",
                func()
                {
                    const url = CABLES.platform.getCablesUrl() + "/edit/" + outer.storage.blueprint.patchId;
                    window.open(url, "_blank");
                },
            });
        }
        else
        {
            items.push({
                "title": "Rename",
                func()
                {
                    gui.patchView.focusSubpatchOp(el.dataset.id);
                    CABLES.CMD.PATCH.setOpTitle();
                },
            });

            items.push({
                "title": "Goto Subpatch Op",
                func()
                {
                    gui.patchView.focusSubpatchOp(el.dataset.id);
                },
            });


            if (el.dataset.subpatchver == "2" && el.dataset.blueprintver != 2)
                items.push({
                    "title": "Create op from subpatch",
                    func()
                    {
                        gui.serverOps.createBlueprint2Op(el.dataset.id);
                        // gui.patchView.focusSubpatchOp(el.dataset.id);
                    },
                });

            if (el.dataset.blueprintver == 2)
            {
                items.push({
                    "title": "Save Blueprint Op",
                    func()
                    {
                        const op = gui.patchView.getSubPatchOuterOp(el.dataset.id);


                        gui.serverOps.updateBluePrint2Attachment(op, { "oldSubId": el.dataset.id });
                        // gui.patchView.focusSubpatchOp(el.dataset.id);
                    },
                });
            }
        }
        CABLES.contextMenu.show({ items }, el);
    }

    opContextMenu(el)
    {
        const items = [];

        const opname = this._currentOp.objName;
        const opid = this._currentOp.id;

        items.push({
            "title": "Set title",
            "func": CABLES.CMD.PATCH.setOpTitle,
        });

        items.push({
            "title": "Set default values",
            func()
            {
                gui.patchView.resetOpValues(opid);
            },
        });

        items.push({
            "title": "Bookmark",
            func()
            {
                gui.bookmarks.add();
            }
        });

        items.push({
            "title": "Manage Op Code",
            func()
            {
                CABLES.CMD.PATCH.manageSelectedOp();
            },
        });


        items.push({
            "title": "Clone Op",
            func()
            {
                CABLES.CMD.PATCH.cloneSelectedOp();
            },
        });

        items.push({
            "title": "Show Op Serialized",
            func()
            {
                CABLES.CMD.DEBUG.watchOpSerialized();
            },
        });

        CABLES.contextMenu.show({ items }, el);
    }
}


export default OpParampanel;