VaKeR CYBER ARMY
Logo of a company Server : Apache/2.4.41 (Ubuntu)
System : Linux absol.cf 5.4.0-198-generic #218-Ubuntu SMP Fri Sep 27 20:18:53 UTC 2024 x86_64
User : www-data ( 33)
PHP Version : 7.4.33
Disable Function : pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare,
Directory :  /var/www/html/libs/absol-form/js/layouteditor/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Current File : /var/www/html/libs/absol-form/js/layouteditor/LayoutEditor.js
import Assembler from '../core/Assembler';
import Dom from 'absol/src/HTML5/Dom';

import FCore from '../core/FCore';
import '../dom/HLine';
import '../dom/VLine';
import R from '../R';
import BaseEditor from '../core/BaseEditor';
import UndoHistory from '../editor/UndoHistory';
import ComponentPropertiesEditor from '../editor/ComponentPropertiesEditor';
import ComponentOutline from '../editor/ComponentOutline';
import LayoutEditorCMD, {LayoutEditorCmdDescriptors, LayoutEditorCmdTree} from './LayoutEditorCmd';
import ClipboardManager from '../ClipboardManager';
import EventEmitter from 'absol/src/HTML5/EventEmitter';
import Rectangle from 'absol/src/Math/Rectangle';
import RelativeAnchorEditor from '../anchoreditors/RelativeAnchorEditor';
import FmFragment, {makeFmFragmentClass} from "../core/FmFragment";
import BaseComponent from "../core/BaseComponent";
import OOP from "absol/src/HTML5/OOP";
import ResizeSystem from "absol/src/HTML5/ResizeSystem";
import CMDTool from "../fragment/CMDTool";
import {randomUniqueIdent} from "../core/utils";
import safeThrow from "absol/src/Code/safeThrow";

var _ = FCore._;
var $ = FCore.$;

function LayoutEditor() {
    BaseEditor.call(this);
    Assembler.call(this);
    this._bindEvent();
    this.CMDTool = new CMDTool();
    this._softScale = 1;
    var self = this;
    this.rootLayout = null;
    this.editingLayout = null;
    this.lastCommitData = {
        editing: null,
        data: null,
        selected: []
    };
    this.setContext(R.LAYOUT_EDITOR, this);
    this.setContext(R.HAS_CMD_EDITOR, this);
    this.setContext(R.CMD_TOOL, this.CMDTool);


    //setup cmd
    this.cmdRunner.assign(LayoutEditorCMD);
    Object.keys(LayoutEditorCmdDescriptors).forEach(function (cmd) {
        if (LayoutEditorCmdDescriptors[cmd].bindKey)
            self.bindKeyToCmd(LayoutEditorCmdDescriptors[cmd].bindKey.win, cmd);
    });

    /**
     * @type {Array<AnchorEditor>}
     */
    this.anchorEditors = [];

    this.undoHistory = new UndoHistory();
    this.undoHistory.on('checkout', function (event) {
        var editing = event.item.data.editing;
        var selected = event.item.data.selected;
        self.applyData(event.item.data.data);
        self.lastCommitData = event.item.data;
        if (editing) {
            self.editLayoutByName(editing);
        } else {
            self.editLayout(self.rootLayout);
        }
        self.setActiveComponentById.apply(self, selected);
        self.updateAnchor();
        self.notifyCmdDescriptorsChange();
        self.notifyUnsaved();
    });
    this.setContext(R.UNDO_HISTORY, this.undoHistory);// because it had it's ContextManager

    this.componentOtline = new ComponentOutline();
    this.setContext(R.COMPONENT_OUTLINE, this.componentOtline);


    this.componentPropertiesEditor = new ComponentPropertiesEditor(this)
        .on({
            change: function (event) {
                self.updateAnchorPosition();
                self.updateEditing();
                Dom.updateResizeSystem();
                if (event.name && event.name.match(/vAlign|hAlign|top|bottom|left|right/)) {
                    self.updateAnchor();
                    self.updateEditing();
                }
            },
            stopchange: function (event) {
                var compName = event.object ? event.object.getAttribute('name') : ('{' + event.objects.map(function (object) {
                    return object.getAttribute('name')
                }).join(', ') + '}');
                self.commitHistory('edit', compName + '.' + event.name + '');
                self.notifyUnsaved();
                if (event.name === 'name') {
                    self.componentOtline.updateComponentTree();
                }
            }
        });
    this.CMDTool.bindWithEditor(this);
}

OOP.mixClass(LayoutEditor, Assembler, BaseEditor);

LayoutEditor.prototype.refresh = function () {
    if (!this.rootLayout) return;
    var data = this.getData();
    var selected = this.getSelected();
    var editing = this.editingLayout.getAttribute('name');
    this.applyData(data);
    if (editing) {
        this.editLayoutByName(editing);
    } else {
        this.editLayout(this.rootLayout);
    }
    this.setActiveComponentByName.apply(this, selected);
    this.updateAnchor();
    this.notifyCmdDescriptorsChange();
};


LayoutEditor.prototype._bindEvent = function () {
    for (var key in this) {
        if (key.startsWith('ev_')) {
            this[key] = this[key].bind(this);
        }
    }
};

LayoutEditor.prototype.constructor = LayoutEditor;

LayoutEditor.prototype.setSoftScale = function (val) {
    //todo
    this._softScale = val;
    this.$editorSpace.addStyle('transform', 'scale(' + val + ')');
};


LayoutEditor.prototype.getSoftScale = function (val) {
    return this._softScale;
};


LayoutEditor.prototype.zoomBy = function (val) {
    this.setSoftScale(val * this._softScale);
};


LayoutEditor.prototype.onAttached = function () {
    this.componentPropertiesEditor.attach(this);
    this.undoHistory.attach(this);
    this.componentOtline.attach(this);


};


LayoutEditor.prototype.onStart = function () {
    this.undoHistory.start();
    this.componentPropertiesEditor.start();
    this.componentOtline.start();
    this.CMDTool.start();
};


LayoutEditor.prototype.onResume = function () {
    /**
     * @type {import('../editor/FormEditor').default}
     */
    this.formEditor = this.getContext(R.FORM_EDITOR);

    this.selfHolder = this.formEditor.getEditorHolderByEditor(this);
    /**
     * @type {import('../editor/UndoHistory').default}
     */
    this.undoHistory.resume();
    this.componentPropertiesEditor.resume();
    this.componentOtline.resume();
    /**
     * @type {import('../fragment/CMDTool'.default)}
     */

    ClipboardManager.on('set', this.ev_clipboardSet);
    this.getView().focus();
    this.statusBarElt = this.getContext(R.STATUS_BAR_ELT);
    this.$mouseOffsetStatus.addTo(this.statusBarElt.$rightCtn);
};


LayoutEditor.prototype.onPause = function () {
    // release undoHistory
    this.undoHistory.pause();
    this.componentPropertiesEditor.pause();
    this.componentOtline.pause();

    if (this.CMDTool) {
        this.CMDTool.pause();
    }
    ClipboardManager.off('set', this.ev_clipboardSet);
    this.getView().blur();
    this.$mouseOffsetStatus.remove();

    //
};

LayoutEditor.prototype.onStop = function () {
    this.undoHistory.stop();
    this.componentPropertiesEditor.stop();
    this.componentOtline.stop();
};

LayoutEditor.prototype.onDestroy = function () {
    this.undoHistory.destroy();
    this.componentPropertiesEditor.destroy();
    this.componentOtline.destroy();
    if (this.autoDestroyInt > 0) {
        clearInterval(this.autoDestroyInt);
    }
};

/**
 * call whenever component is edited, event will not be fired if disable publicDataChange flag
 */
LayoutEditor.prototype.notifyDataChange = function () {
    if (this._publicDataChange)
        this.emit('change', {type: 'change', target: this}, this);
};


LayoutEditor.prototype.createView = function () {
    var self = this;
    this.$view = _({
        class: ['as-layout-editor'],
        attr: {tabindex: '1'},
        child: [
            {
                class: 'as-layout-editor-left',
                child: [{
                    class: 'as-layout-editor-header',
                    child: [
                        {
                            class: 'as-layout-editor-cmd-tool-container',
                            child: this.CMDTool.getView()
                        },
                        {
                            class: 'as-layout-editor-quickpath-container',
                            child: {
                                tag: 'quickpath',
                                on: {
                                    change: this.ev_quickpathChange.bind(this)
                                }
                            }
                        }
                    ]
                },
                    {
                        class: 'as-layout-editor-body',
                        child: [
                            {
                                class: 'as-layout-editor-measure-container',
                                child: [
                                    {
                                        class: 'as-layout-editor-vrule-container',
                                        child: 'vruler'
                                    },
                                    {
                                        class: 'as-layout-editor-hrule-container',
                                        child: 'hruler'
                                    },
                                    {
                                        class: 'as-layout-editor-background-container'
                                    },
                                    {
                                        class: ["as-layout-editor-space-container", 'absol-bscroller'],
                                        child: {
                                            class: 'as-layout-editor-space',
                                            child: [
                                                {
                                                    class: 'as-layout-editor-layout-container'
                                                },
                                                {
                                                    class: 'as-layout-editor-forceground-container',
                                                    child: '.as-layout-editor-forceground',
                                                    extendEvent: 'contextmenu',
                                                    on: {
                                                        contextmenu: this.ev_contextMenuLayout.bind(this)
                                                    }
                                                }
                                            ]
                                        }
                                    }
                                ]
                            }

                        ]
                    }]
            },

            {
                class: 'as-layout-editor-tool-tab-ctn',
                child: [
                    {
                        class: 'as-layout-editor-tool-tab-bar',
                        child: [
                            {
                                tag: 'button',
                                attr: {
                                    'data-tab': 'history'
                                },
                                class: ['as-layout-editor-tool-tab-btn'],
                                child: 'history-ico',
                                on: {
                                    click: this.toggleToolTab.bind(this, 'history')
                                }
                            },
                            {
                                tag: 'button',
                                attr: {
                                    'data-tab': 'properties'
                                },
                                class: 'as-layout-editor-tool-tab-btn',
                                child: 'properties-ico',
                                on: {
                                    click: this.toggleToolTab.bind(this, 'properties')
                                }
                            }
                        ]
                    },
                    {
                        class: 'as-layout-editor-tool-ctn',
                        attr: {
                            'data-tab': 'history'
                        },
                        style: {display: 'none'},
                        child: this.undoHistory.getView()
                    },
                    {
                        class: 'as-layout-editor-tool-ctn',
                        attr: {
                            'data-tab': 'properties'
                        },
                        style: {display: 'none'},
                        child: this.componentPropertiesEditor.getView()
                    },

                ]
            }
        ],
        on: {
            keydown: this.ev_cmdKeyDown.bind(this),
            mousemove: this.ev_mouseMove
        }
    });


    this.$header = $('.as-layout-editor-header', this.$view);
    this.$quickpath = $('quickpath', this.$header);

    this.$attachHook = _('attachhook').on('error', function () {
        this.updateSize = self.updateSize.bind(self);
        Dom.addToResizeSystem(this);
        self.updateSize();
    }).addTo(this.$view);

    this.$hruler = $('hruler', this.$view);
    this.$hruler.measureElement($('.as-relative-layout', this.$view));
    this.$spaceCtn = $('.as-layout-editor-space-container', this.$view)
        .on('scroll', this.ev_layoutCtnScroll.bind(this));
    this.$hrulerMouse = _('.as-hruler-mouse').addTo(this.$hruler);
    this.$hrulerEditing = _('.as-hruler-editing').addTo(this.$hruler);


    this.$vruler = $('vruler', this.$view);
    this.$vruler.measureElement($('.as-relative-layout', this.$view));
    this.$vrulerMouse = _('.as-vruler-mouse').addTo(this.$vruler);
    this.$vrulerEditing = _('.as-vruler-editing').addTo(this.$vruler);


    this.$layoutCtn = $('.as-layout-editor-layout-container', this.$view);

    this.$forceground = $('.as-layout-editor-forceground', this.$view)
        .on('mousedown', this.ev_mousedownForceGround.bind(this));

    this.$editorSpaceCtn = $('.as-layout-editor-space-container', this.$view)
        .on('click', this.ev_clickEditorSpaceCtn);
    this.autoDestroyInt = setInterval(function () {
        if (!self.$view.isDescendantOf(document.body)) {
            self.destroy();
        }
    }, 6900);
    this.$editorSpace = $('.as-layout-editor-space', this.$view)

    this.$mouseSelectingBox = _('.as-layout-editor-mouse-selecting-box');

    this.$curtainLeft = _('.as-layout-editor-curtain')
        .on('dblclick', this.ev_dblclickCurtain)
        .addTo(this.$forceground);
    this.$curtainRight = _('.as-layout-editor-curtain')
        .on('dblclick', this.ev_dblclickCurtain)
        .addTo(this.$forceground);
    this.$curtainTop = _('.as-layout-editor-curtain')
        .on('dblclick', this.ev_dblclickCurtain)
        .addTo(this.$forceground);
    this.$curtainBottom = _('.as-layout-editor-curtain')
        .on('dblclick', this.ev_dblclickCurtain)
        .addTo(this.$forceground);

    this.$cmdToolCtn = $('.as-layout-editor-cmd-tool-container', this.$view);
    this.$measureCtn = $('.as-layout-editor-measure-container', this.$view);
    this.$mouseOffsetStatus = _({
        tag: 'button',
        class: 'as-status-bar-item',
        child: [
            'span.mdi.mdi-cursor-move',
            'span',
            'span'
        ]
    });

    this._tabToolHolders = {
        history: {
            $tabBtn: $('.as-layout-editor-tool-tab-btn[data-tab="history"]', this.$view),
            $ctn: $('.as-layout-editor-tool-ctn[data-tab="history"]', this.$view)
        },
        properties: {
            $tabBtn: $('.as-layout-editor-tool-tab-btn[data-tab="properties"]', this.$view),
            $ctn: $('.as-layout-editor-tool-ctn[data-tab="properties"]', this.$view)
        }
    };

    this._activatedToolTab = null;

    this.$view.layoutEditor = this;

};


LayoutEditor.prototype.toggleToolTab = function (name) {
    var holder = this._tabToolHolders[this._activatedToolTab];
    if (holder) {
        holder.$tabBtn.removeClass('active');
        holder.$ctn.addStyle('display', 'none');
    }
    holder = this._tabToolHolders[name];
    if (!holder) {
        name = null;
    }
    if (name === this._activatedToolTab) {
        this._activatedToolTab = null;
        return;
    }
    this._activatedToolTab = name;
    holder.$tabBtn.addClass('active');
    holder.$ctn.removeStyle('display');
    ResizeSystem.update();

};

LayoutEditor.prototype.ev_quickpathChange = function (event) {
    var layout = event.item.layout;
    var thisEditor = this;
    setTimeout(function () {
        thisEditor.editLayout(layout);
    }, 10);
};

/**
 * @param {MouseEvent} event
 */
LayoutEditor.prototype.ev_mouseMove = function (event) {
    var vruleBound = this.$vruler.getBoundingClientRect();
    var hruleBound = this.$hruler.getBoundingClientRect();
    this.$vrulerMouse.addStyle('top', event.clientY - vruleBound.top - 1 - 1 + 'px');
    this.$hrulerMouse.addStyle('left', event.clientX - hruleBound.left - 1 - 1 + 'px');
    this.mouseClientX = event.clientX;
    this.mouseClientY = event.clientY;
    if (this.rootLayout) {
        var rootBound = this.rootLayout.domElt.getBoundingClientRect();
        this.mouseOffsetX = Math.round(event.clientX - rootBound.left);
        this.mouseOffsetY = Math.round(event.clientY - rootBound.top);
        this.$mouseOffsetStatus.children[1].innerHTML = this.mouseOffsetX + ',' + this.mouseOffsetY;
    }
    //update  mouse position on 
};


LayoutEditor.prototype.ev_clickEditorSpaceCtn = function (event) {
    if (event.target === this.$editorSpaceCtn) {
        this.setActiveComponent();
    }
};


LayoutEditor.prototype.ev_mousedownForceGround = function (event) {
    if (!EventEmitter.isMouseLeft(event)) return;
    if (event.target !== this.$forceground) return;
    var hitComponent = this.findComponentsByMousePosition(event.clientX, event.clientY);
    if (hitComponent) {
        if (event.shiftKey)
            this.toggleActiveComponent(hitComponent);
        else
            this.setActiveComponent(hitComponent);
        var anchorEditor = this.anchorEditors[this.anchorEditors.length - 1];

        //cheating
        var repeatedEvent = EventEmitter.copyEvent(event, {
            target: $('.as-resize-box-body', anchorEditor.$resizeBox),
            preventDefault: event.preventDefault.bind(event)
        });
        anchorEditor.$resizeBox.eventHandler.mouseDownBody(repeatedEvent);
        this.$view.focus();// layouteditor may be not focus before, prevent default effect make it not focus

        // prevent auto toggle with click event
        anchorEditor.preventClick = true;
        anchorEditor.once('click', function () {
            setTimeout(function () {
                anchorEditor.preventClick = false;
            }, 1)
        });
        anchorEditor.$resizeBox.on('endmove', function () {
            anchorEditor.preventClick = false;
        });
    } else {
        $(document.body).on('mouseup', this.ev_mouseFinishForceGround)
            .on('mouseleave', this.ev_mouseFinishForceGround)
            .on('mousemove', this.ev_mouseMoveForceGround);
        this.$editorSpaceCtn.off('click', this.ev_clickEditorSpaceCtn);
        this.$mouseSelectingBox.addTo(this.$forceground);
        var forcegroundBound = this.$forceground.getBoundingClientRect();
        this._forgroundMovingData = {
            left: event.clientX - forcegroundBound.left,
            top: event.clientY - forcegroundBound.top,
            width: 0,
            height: 0,
            event0: event
        };
        this.$mouseSelectingBox.addStyle({
            left: this._forgroundMovingData.left + 'px',
            top: this._forgroundMovingData.top + 'px',
            width: '0',
            height: '0'
        });
    }
};


LayoutEditor.prototype.ev_mouseMoveForceGround = function (event) {
    var forcegroundBound = this.$forceground.getBoundingClientRect();
    this._forgroundMovingData.width = event.clientX - forcegroundBound.left - this._forgroundMovingData.left;
    this._forgroundMovingData.height = event.clientY - forcegroundBound.top - this._forgroundMovingData.top;
    if (this._forgroundMovingData.width < 0) {
        this.$mouseSelectingBox.addStyle({
            left: this._forgroundMovingData.left + this._forgroundMovingData.width + 'px',
            width: -this._forgroundMovingData.width + 'px',
        });
    } else {
        this.$mouseSelectingBox.addStyle({
            left: this._forgroundMovingData.left + 'px',
            width: this._forgroundMovingData.width + 'px',
        });
    }
    if (this._forgroundMovingData.height < 0) {
        this.$mouseSelectingBox.addStyle({
            top: this._forgroundMovingData.top + this._forgroundMovingData.height + 'px',
            height: -this._forgroundMovingData.height + 'px',
        });
    } else {
        this.$mouseSelectingBox.addStyle({
            top: this._forgroundMovingData.top + 'px',
            height: this._forgroundMovingData.height + 'px',
        });
    }
};


LayoutEditor.prototype.ev_mouseFinishForceGround = function (event) {

    $(document.body).off('mouseup', this.ev_mouseFinishForceGround)
        .off('mouseleave', this.ev_mouseFinishForceGround)
        .off('mousemove', this.ev_mouseMoveForceGround);
    setTimeout(this.$editorSpaceCtn.on.bind(this.$editorSpaceCtn, 'click', this.ev_clickEditorSpaceCtn), 10);
    this.$mouseSelectingBox.remove();
    //find all rectangle
    var selectedComp = [];
    var event0 = this._forgroundMovingData.event0;
    var left = Math.min(event0.clientX, event.clientX);
    var right = Math.max(event0.clientX, event.clientX);
    var top = Math.min(event0.clientY, event.clientY);
    var bottom = Math.max(event0.clientY, event.clientY);

    var selectRect = new Rectangle(left, top, right - left, bottom - top);

    var children = this.editingLayout.children;

    if (this.anchorEditors.length > 0) {
        children = this.editingLayout.children;
    }
    var comp, compRect;

    for (var i = 0; i < children.length; ++i) {
        comp = children[i];
        compRect = Rectangle.fromClientRect(comp.view.getBoundingClientRect());
        if (compRect.isCollapse(selectRect))
            selectedComp.push(comp);
    }

    var self = this;

    if (event.shiftKey) {
        this.toggleActiveComponent.apply(this, selectedComp.filter(function (comp) {
            return !self.findAnchorEditorByComponent(comp);
        }));
    } else {
        this.setActiveComponent.apply(this, selectedComp);
    }
};


LayoutEditor.prototype.ev_layoutCtnScroll = function () {
    this.updateRuler();
};


LayoutEditor.prototype.ev_contextMenuLayout = function (event) {
    var self = this;
    if (event.target === this.$forceground) {
        event.showContextMenu({
            // play-box-outline
            items: [
                {
                    text: 'Preview',
                    icon: 'span.mdi.mdi-pencil-box-multiple-outline',
                    cmd: 'preview'
                }
            ],
            extendStyle: {
                fontSize: '12px'
            }
        }, function (menuEvent) {
            var cmd = menuEvent.menuItem.cmd;
            self.execCmd(cmd);
        });
    }
};


LayoutEditor.prototype.ev_clipboardSet = function () {
    this.notifyCmdDescriptorsChange();
};


LayoutEditor.prototype.ev_dblclickCurtain = function (event) {
    var parentLayout = this.findNearestLayoutParent(this.editingLayout.parent);
    if (parentLayout && parentLayout.isLayout) this.editLayout(parentLayout);
};


LayoutEditor.prototype.updateEditing = function () {
    var hruleBound = this.$hruler.getBoundingClientRect();
    var vruleBound = this.$vruler.getBoundingClientRect();
    var editingBound = this.editingLayout.view.getBoundingClientRect();
    this.$hrulerEditing.addStyle({
        left: editingBound.left - hruleBound.left - 1 + 'px',
        width: editingBound.width + 'px'
    });

    this.$vrulerEditing.addStyle({
        top: editingBound.top - vruleBound.top - 1 + 'px',
        height: editingBound.height + 'px'
    });

    var foregroundBound = this.$forceground.getBoundingClientRect();
    if (this.editingLayout === this.rootLayout) {
        this.$curtainLeft.addStyle({
            visibility: 'hidden'
        });
        this.$curtainBottom.addStyle({
            visibility: 'hidden'
        });

        this.$curtainRight.addStyle({
            visibility: 'hidden'
        });

        this.$curtainTop.addStyle({
            visibility: 'hidden'
        });
    } else {
        this.$curtainLeft.addStyle({
            left: '0',
            top: '0',
            width: editingBound.left - foregroundBound.left + 'px',
            height: editingBound.bottom - foregroundBound.top + 'px'
        }).removeStyle('visibility');

        this.$curtainBottom.addStyle({
            left: '0',
            top: editingBound.bottom - foregroundBound.top + 'px',
            width: editingBound.right - foregroundBound.left + 'px',
            height: foregroundBound.bottom - editingBound.bottom + 'px'
        }).removeStyle('visibility');

        this.$curtainRight.addStyle({
            left: editingBound.right - foregroundBound.left + 'px',
            top: editingBound.top - foregroundBound.top + 'px',
            width: foregroundBound.right - editingBound.right + 'px',
            height: foregroundBound.bottom - editingBound.top + 'px'
        }).removeStyle('visibility');

        this.$curtainTop.addStyle({
            left: editingBound.left - foregroundBound.left + 'px',
            top: '0',
            width: foregroundBound.right - editingBound.left + 'px',
            height: editingBound.top - foregroundBound.top + 'px'
        }).removeStyle('visibility');
    }
};

LayoutEditor.prototype.updateRuler = function () {
    this.$vruler.update();
    this.$hruler.update();

    var hruleBound = this.$hruler.getBoundingClientRect();
    var vruleBound = this.$vruler.getBoundingClientRect();
    var editingBound = this.editingLayout.view.getBoundingClientRect();
    this.$hrulerEditing.addStyle({
        left: editingBound.left - hruleBound.left - 1 + 'px',
        width: editingBound.width + 'px'
    });

    this.$vrulerEditing.addStyle({
        top: editingBound.top - vruleBound.top - 1 + 'px',
        height: editingBound.height + 'px'
    });
};

LayoutEditor.prototype.updateAnchor = function () {
    for (var i = 0; i < this.anchorEditors.length; ++i) {
        this.anchorEditors[i].update();
    }
};


LayoutEditor.prototype.updateAnchorPosition = function () {
    for (var i = 0; i < this.anchorEditors.length; ++i) {
        this.anchorEditors[i].updatePosition();
    }
};

LayoutEditor.prototype.findComponentsByName = function (name, from) {
    from = from || this.rootLayout;
    if (!from) return;
    var res = undefined;
    if (from.attributes.name === name) {
        return [from];
    }
    var self = this;
    if (from.children)
        res = from.children.reduce(function (ac, child) {
            var found = self.findComponentsByName(name, child);
            if (found) return ac.concat(found);
            return ac;
        }, []);
    if (res.length > 0) return res;
    return undefined;
};

LayoutEditor.prototype.findComponentsById = function (id, from) {
    from = from || this.rootLayout;
    if (!from) return undefined;
    var res = undefined;
    if (from.attributes.id === id) {
        return [from];
    }
    var self = this;
    if (from.children) {
        res = from.children.reduce(function (ac, child) {
            var found = self.findComponentsByName(id, child);
            if (found) return ac.concat(found);
            return ac;
        }, []);
    }
    if (res.length > 0) return res;
    return undefined;
};

LayoutEditor.prototype.findComponentsByMousePosition = function (clientX, clientY) {
    var children = this.editingLayout.children;
    var child, childBound;
    for (var i = children.length - 1; i >= 0; --i) {
        child = children[i];
        childBound = child.view.getBoundingClientRect();
        if (clientX >= childBound.left && clientX <= childBound.right && clientY >= childBound.top && clientY <= childBound.bottom)
            return child;
    }
    return undefined;
};

LayoutEditor.prototype.updateSize = function () {
};


LayoutEditor.prototype._newAnchorEditor = function (component) {
    var self = this;
    var AnchorEditor = component.parent ? component.parent.getAnchorEditorConstructor() : RelativeAnchorEditor;
    //craete new, repeat event to other active anchor editor
    var editor = new AnchorEditor(this).on('click', function (event) {
        if (editor.preventClick) return;
        if (this.component)
            if (event.shiftKey) {
                self.toggleActiveComponent(this.component)
            } else
                self.setActiveComponent(this.component);
    })//todo: implement in AnchorEditor
        .on('beginmove', function (event) {
            var repeatEvent = event.repeatEvent;
            var other;
            for (var i = 0; i < self.anchorEditors.length; ++i) {
                other = self.anchorEditors[i];
                if (other != this) {
                    other.ev_beginMove(false, repeatEvent);
                }
            }
        })
        .on('moving', function (event) {
            var repeatEvent = event.repeatEvent;
            var other;
            for (var i = 0; i < self.anchorEditors.length; ++i) {
                other = self.anchorEditors[i];
                if (other != this) {
                    other.ev_moving(false, repeatEvent);
                }
            }
            self.notifyDataChange();
            self.componentPropertiesEditor.styleEditor.updatePropertyRecursive('width');
            self.componentPropertiesEditor.styleEditor.updatePropertyRecursive('height');
            self.componentPropertiesEditor.allPropertyEditor.updatePropertyRecursive('width');
            self.componentPropertiesEditor.allPropertyEditor.updatePropertyRecursive('height');
            self.updateEditing();
        })
        .on('endmove', function (event) {
            var originEvent = event.originEvent;
            var other;
            for (var i = 0; i < self.anchorEditors.length; ++i) {
                other = self.anchorEditors[i];
                if (other != this) {
                    other.ev_endMove(false, originEvent);
                }
            }
            self.commitHistory('move', 'Move/Resize component');
            self.notifyUnsaved();
        })
        .on('focus', function (event) {
            self.componentPropertiesEditor.edit.apply(self.componentPropertiesEditor, self.getActivatedComponents());
            self.emit('focuscomponent', {
                type: 'focuscomponent',
                component: this.component,
                originEvent: event,
                target: self
            }, self);
        })
        .on('change', function (event) {
            self.notifyDataChange();
        });
    editor.edit(component)
    return editor;
};

/**
 * @argument {Array<import('../core/BaseComponent').default>}
 */
LayoutEditor.prototype.setActiveComponent = function () {
    var components = Array.prototype.slice.call(arguments);
    var oldEditors = this.anchorEditors.slice();
    var oldComponents = this.anchorEditors.map(function (editor) {
        return editor.component;
    });

    //todo
    while (this.anchorEditors.length > 0) {
        var editor = this.anchorEditors.pop();
    }

    var oldIndex;
    var component;
    var editor;
    while (this.anchorEditors.length < components.length) {
        component = components[this.anchorEditors.length];
        oldIndex = oldComponents.indexOf(component);
        if (oldIndex >= 0) {
            editor = oldEditors[oldIndex];
            oldEditors[oldIndex] = null;// for removing
        } else {
            editor = this._newAnchorEditor(components[this.anchorEditors.length]);
        }
        this.anchorEditors.push(editor);
    }
    oldEditors.forEach(function (editor) {
        editor && editor.edit();
    });
    if (this.anchorEditors.length > 0)
        this.anchorEditors[this.anchorEditors.length - 1].focus();
    this.componentOtline.updateComponentStatus();
    this.lastCommitData.selected = this.getSelected();
    this.lastCommitData.editing = this.editingLayout.getAttribute('name');
    this.emit('selectedcomponentchange', {target: this, type: 'selectedcomponentchange'}, this);
    this.notifyCmdDescriptorsChange();
};

LayoutEditor.prototype.setActiveComponentByName = function () {
    var components = Array(arguments.length).fill(null);
    var dict = Array.prototype.reduce.call(arguments, function (ac, cr, i) {
        ac[cr] = i;
        return ac;
    }, {});

    function visit(node) {
        var name = node.getAttribute('name');
        if (typeof dict[name] == 'number') {
            components[dict[name]] = node;
        }
        node.children.forEach(visit);
    }

    visit(this.rootLayout);
    this.setActiveComponent.apply(this, components);
};


LayoutEditor.prototype.setActiveComponentById = function () {
    var components = Array(arguments.length).fill(null);
    var dict = Array.prototype.reduce.call(arguments, function (ac, cr, i) {
        ac[cr] = i;
        return ac;
    }, {});

    function visit(node) {
        var id = node.attributes.id;
        if (typeof dict[id] == 'number') {
            components[dict[id]] = node;
        }
        node.children.forEach(visit);
    }

    visit(this.rootLayout);
    components = components.filter(function (c) {
        return !!c
    });
    this.setActiveComponent.apply(this, components);
};

/**
 * @argument {Array<import('../core/BaseComponent').default>}
 */
LayoutEditor.prototype.toggleActiveComponent = function () {
    //todo
    var editor;
    var focusEditor = undefined;
    for (var i = 0; i < arguments.length; ++i) {
        editor = this.findAnchorEditorByComponent(arguments[i]);
        if (editor) {
            editor.edit(undefined);
        } else {
            editor = this._newAnchorEditor(arguments[i]);
            this.anchorEditors.push(editor);
            focusEditor = editor;
        }
    }

    this.anchorEditors = this.anchorEditors.filter(function (e) {
        return !!e.component;
    });

    if (this.anchorEditors.length > 0) {
        focusEditor = this.anchorEditors[this.anchorEditors.length - 1];
    }
    if (focusEditor) focusEditor.focus();
    this.componentOtline.updateComponentStatus();
    this.lastCommitData.selected = this.getSelected();
    this.lastCommitData.editing = this.editingLayout.getAttribute('name');
    this.emit('selectedcomponentchange', {target: this, type: 'selectedcomponentchange'}, this);
    this.notifyCmdDescriptorsChange();
};


LayoutEditor.prototype.findAnchorEditorByComponent = function (comp) {
    for (var i = 0; i < this.anchorEditors.length; ++i) {
        if (this.anchorEditors[i].component === comp) return this.anchorEditors[i];
    }
    return undefined;
};


LayoutEditor.prototype.findFocusAnchorEditor = function () {
    // faster
    for (var i = this.anchorEditors.length - 1; i >= 0; --i) {
        if (this.anchorEditors[i].isFocus) return this.anchorEditors[i];
    }
    return null;
};


LayoutEditor.prototype.getActivatedComponents = function () {
    return this.anchorEditors.map(function (e) {
        return e.component;
    }).filter(function (e) {
        return !!e
    });
};


LayoutEditor.prototype.applyData = function (data) {
    this._originData = data;
    var layout = data.layout || data;
    var FmClass = makeFmFragmentClass({
        tag: 'LayoutTest',
        contentViewData: {
            layout: layout
        }
    });
    this.rootFragment = new FmClass();
    this.rootLayout = this.rootFragment.view;
    this.$layoutCtn.clearChild().addChild(this.rootLayout.domElt);
    this.rootLayout.onAttached(this);
    this.$vruler.measureElement(this.rootLayout.domElt);
    this.$hruler.measureElement(this.rootLayout.domElt);
    this.editLayout(this.rootLayout);
    this.componentOtline.updateComponentTree();
    this.emit('change', {type: 'change', target: this, data: data}, this);
};


LayoutEditor.prototype.setData = function (data) {
    this.applyData(this.normalizeData(data));
    this.commitHistory('set-data', "Set data");
};

LayoutEditor.prototype.normalizeData = function (data) {
    var layout = data.layout || data;
    var version = '1.1.0';
    var circuit = data.circuit || {};
    circuit.blocks = circuit.blocks || [];
    circuit.lines = circuit.lines || [];
    circuit.diagram = circuit.diagram || {};
    circuit.blocks.forEach(function (block) {
        block.attributes.id = block.attributes.id || randomUniqueIdent();
        block.attributes.name = block.attributes.name || block.attributes.id;
    });

    var twoWayLines = [];
    circuit.lines.forEach(function (line) {
        line.id = line.id || randomUniqueIdent();
        if (line.twoWay) {
            delete line.twoWay;
            twoWayLines.push(Object.assign({}, line, {
                u: line.v,
                v: line.u,
                uPin: line.vPin,
                vPin: line.uPin,
            }));
        }
    });
    circuit.lines.push.apply(circuit.lines, twoWayLines);
    var id2Name = {};
    var name2Id = {};
    var idList = {};

    function visit(node) {
        var name = node.attributes && node.attributes.name;
        var id = node.attributes && node.attributes.id;
        if  (!id){
            if (!node.attributes) {
                node.attributes = {};
            }
            id = randomUniqueIdent();
            node.attributes.id = id;
        }
        if (idList[id]) {
            id = randomUniqueIdent();
            if (!node.attributes) {
                node.attributes = {};
            }
            node.attributes.id = id;
        }
        idList[id] = true;
        if (id && name) {
            id2Name[id] = name;
            name2Id[name] = id;
        }
        if (node.children) node.children.forEach(visit);
    }

    visit(layout);
    if (circuit.blocks)
        circuit.blocks.forEach(visit);
    circuit.lines.forEach(function (line) {
        if (name2Id[line.u]) {
            line.u = name2Id[line.u];
        }
        if (name2Id[line.v]) {
            line.v = name2Id[line.v];
        }
    });
    circuit.lines = circuit.lines.filter(function (line) {
        if (!idList[line.u] || !idList[line.v]) {
            console.error("invalid line ", line);
        }
        return idList[line.u] && idList[line.v]
    });
    if (circuit.diagram && circuit.diagram.includedBlocks) {
        circuit.diagram.includedBlocks = circuit.diagram.includedBlocks.filter(function (id) {
            return idList[id];
        })
    }

    return {
        app: R.APP,
        version: version,
        layout: layout,
        circuit: circuit,
    };
};

LayoutEditor.prototype.explicitLines = function () {

};


LayoutEditor.prototype.autoExpandRootLayout = function () {
    if (this.rootLayout) {
        var minSize = this.rootLayout.measureMinSize();
        var isChange = false;
        if (minSize.width > this.rootLayout.style.width) {
            this.rootLayout.setStyle('width', minSize.width);
            isChange = true;
        }
        if (minSize.height > this.rootLayout.style.height) {
            this.rootLayout.setStyle('height', minSize.height);
            isChange = true;
        }
        if (isChange) {
            this.emit('layoutexpand', {type: 'layoutexpand', target: this, layout: this.rootLayout}, this);
            this.notifyDataChange();
        }
    }
};


LayoutEditor.prototype.editLayout = function (layout) {
    if (!layout) return;
    if (!layout.isLayout) return;
    var lastTag = this.editingLayout && this.editingLayout.tag;
    var currentTag = layout && layout.tag;
    this.editingLayout = layout;
    this.lastCommitData.editing = layout.getAttribute('name');
    this.$quickpath.path = this.getQuickpathFrom(layout);
    this.setActiveComponent();
    this.updateEditing();
    if (lastTag != currentTag) {
        this.notifyCmdChange();
    }
};

LayoutEditor.prototype.editLayoutByName = function (name) {
    var comps = this.findComponentsByName(name);
    if (comps && comps.length > 0)
        this.editLayout(comps[0]);
};


LayoutEditor.prototype.getQuickpathFrom = function (layout) {
    var thisEditor = this;
    while (layout && !layout.isLayout) {
        layout = layout.parent;
    }
    var res = [];
    if (layout) {
        var childLayoutItems = layout.children.filter(function (comp) {
            return comp.isLayout;
        }).map(function (comp) {
            return {name: comp.getAttribute('name'), icon: comp.menuIcon, layout: comp};
        });
        if (childLayoutItems.length > 0) {
            res.push({
                name: '...',
                items: childLayoutItems
            });
        }
    }

    var node;
    while (layout) {
        node = {name: layout.getAttribute('name'), icon: layout.menuIcon, layout: layout};
        if (layout.parent && layout.parent.children) {
            node.items = layout.parent.children.filter(function (comp) {
                return comp.isLayout;
            }).map(function (comp) {
                return {
                    name: comp.getAttribute('name'),
                    icon: comp.menuIcon,
                    layout: comp,
                    extendStyle: layout == comp ? {color: "#009"} : {}
                };
            });
        } else node.items = [{name: layout.getAttribute('name'), icon: layout.menuIcon, layout: layout}];
        res.unshift(node);
        layout = layout.parent;
    }
    return res;
};

LayoutEditor.prototype.getData = function () {
    var result = null;
    var layout;
    var originData = this._originData;
    if (this.rootLayout) {
        layout = this.rootLayout.getData();
        //new version
        if (originData && originData.layout) {
            result = Object.assign({}, originData, {layout: layout});
        } else {
            result = layout;
        }
    }
    return result;
};

LayoutEditor.prototype.getBlockData = function () {
    var originData = this._originData;
    if (originData.circuit && originData.circuit.blocks) {
        return originData.circuit.blocks;
    } else return [];
};

LayoutEditor.prototype.setBlockData = function (blocks) {
    var originData = this._originData;
    if (originData.layout) {
        originData.circuit = originData.circuit || {};
        originData.circuit.blocks = blocks;
    } else {
        this._originData = {
            app: R.APP,
            version: R.VERSION,
            layout: originData,
            circuit: {
                blocks: blocks,
                lines: []
            }
        };
    }
};


LayoutEditor.prototype.getLineData = function () {
    var originData = this._originData;
    if (originData.circuit && originData.circuit.blocks) {
        return originData.circuit.lines;
    } else return [];
};


LayoutEditor.prototype.setLineData = function (lines) {
    this._originData.lines = lines;
};

LayoutEditor.prototype.setDiagramData = function (diagram) {
    this._originData.circuit.diagram = diagram
};


LayoutEditor.prototype.getDiagramData = function () {
    return this._originData.circuit.diagram || {};
};


LayoutEditor.prototype.getComponentTool = function () {
    return this.getContext(R.COMPONENT_PICKER);
};

LayoutEditor.prototype.getOutlineTool = function () {
    return this.getContext(R.COMPONENT_OUTLINE);
};

LayoutEditor.prototype.getCmdNames = function () {
    return Object.keys(LayoutEditorCMD);
};

LayoutEditor.prototype.getCmdDescriptor = function (name) {
    var descriptor = LayoutEditorCmdDescriptors[name];
    if (this.editingLayout) {
        var anchorEditorConstructor = this.editingLayout.getAnchorEditorConstructor();
        if (anchorEditorConstructor.prototype.getCmdDescriptor) {
            descriptor = descriptor || (anchorEditorConstructor.prototype.getCmdDescriptor.call(null, name));
        }
    }

    var res = Object.assign({
        type: 'trigger',
        desc: 'command: ' + name,
        icon: 'span.mdi.mdi-apple-keyboard-command'
    }, descriptor)
    if ((name.startsWith('align') || name.startsWith('equalise')) && this.anchorEditors.length < 2) {
        res.disabled = true;
    } else if (name.startsWith('distribute') && this.anchorEditors.length < 3) {
        res.disabled = true;
    } else if (name.match(/^(delete|copy|cut|horizontalAlign|verticalAlign)/) && this.anchorEditors.length < 1) {
        res.disabled = true;
    } else if (name == 'paste') {
        res.disabled = !ClipboardManager.get(R.CLIPBOARD.COMPONENTS);
    } else if (name == 'undo') {
        res.disabled = this.undoHistory.lastItemIndex <= 0;
    } else if (name == 'redo') {
        res.disabled = this.undoHistory.lastItemIndex >= this.undoHistory.items.length - 1;
    }

    return res;
};


LayoutEditor.prototype.getCmdGroupTree = function () {
    var tree = [LayoutEditorCmdTree];
    if (this.editingLayout) {
        var anchorEditorConstructor = this.editingLayout.getAnchorEditorConstructor();
        if (anchorEditorConstructor.prototype.getCmdGroupTree) {
            tree.push(anchorEditorConstructor.prototype.getCmdGroupTree.call(null));
        }
    }

    return tree;
};


LayoutEditor.prototype.findNearestLayoutParent = function (comp) {
    while (comp) {
        if (comp.addChildByPosition) {
            break;
        }
        comp = comp.parent;
    }
    return comp;
};

/**
 * @returns {import('../core/BaseComponent') }
 */
LayoutEditor.prototype.addNewComponent = function (constructor, posX, posY) {
    var self = this;
    var layout = this.editingLayout;
    var rootBound = this.rootLayout.domElt.getBoundingClientRect();
    var layoutBound = layout.domElt.getBoundingClientRect();
    var layoutPosX = posX - (layoutBound.left - rootBound.left);
    var layoutPosY = posY - (layoutBound.top - rootBound.top);
    var addedComponents = [];
    if (!(constructor instanceof Array)) {
        constructor = [constructor];
    }

    addedComponents = constructor.map(function (cst) {
        var comp;
        if (typeof cst == 'function') {
            if (cst.prototype.type === FmFragment.prototype.type) {
                var frg = new cst();
                comp = frg.view;
                self.rootFragment.addChild(frg);
            } else if (cst.prototype.type === BaseComponent.prototype.type) {
                comp = new cst();
                comp.fragment = self.rootFragment;
            }
        } else {
            comp = self.buildComponent(cst, self.rootFragment);
        }
        layout.addChildByPosition(comp, layoutPosX, layoutPosY);
        return comp;
    });

    this.emit('addcomponent', {type: 'addcomponent', components: addedComponents, target: this}, this);
    this.setActiveComponent.apply(this, addedComponents);
    this.notifyDataChange();
    setTimeout(this.updateAnchorPosition.bind(this), 1);
    this.updateEditing();
    this.componentOtline.updateComponentTree();
    this.commitHistory('add', "Add " + addedComponents.map(function (comp) {
        return comp.getAttribute('name');
    }).join(', '));
    this.componentOtline.updateComponentTree();
    this.notifyUnsaved();
};


LayoutEditor.prototype.buildComponent = function () {
    var comp = Assembler.prototype.buildComponent.apply(this, arguments);
    var thisEditor = this;
    var originFunction = comp.getStyle;
    comp.getStyle = function () {
        var res = originFunction.apply(this, arguments);
        if (arguments[1] == 'px' && comp.style[arguments[0]] != res) {
            res /= thisEditor._softScale;
        }
        return res;
    };
    return comp;
};

LayoutEditor.prototype.clearRootLayout = function () {
    this._activatedComponent = undefined;
    this.rootLayout.clearChild();
    this.updateAnchor();
    this.emit('clearallcomponent', {target: this}, this);
    this.notifyDataChange();
    this.componentOtline.updateComponentTree();
    this.commitHistory('remove', 'Remove all components');
    this.notifyUnsaved();
};


LayoutEditor.prototype.removeComponent = function () {
    var removedComponents = Array.prototype.slice.call(arguments);
    this.toggleActiveComponent.apply(this, removedComponents);
    removedComponents.forEach(function (comp) {
        comp.remove();
    })
    //edit nothing
    this.emit('removecomponent', {type: 'removecomponent', target: this, components: removedComponents}, this);
    this.componentPropertiesEditor.edit();
    this.notifyDataChange();

    if (removedComponents.length > 0) {
        this.componentOtline.updateComponentTree();
        this.commitHistory('remove', 'Remove ' + removedComponents.map(function (c) {
            return c.getAttribute('name');
        }).join(', '));
        this.notifyUnsaved();
    }
};


LayoutEditor.prototype.moveUpComponent = function (comp) {
    var parent = comp.parent;
    if (!parent) return;
    var prevChild = parent.findChildBefore(comp);
    if (!prevChild) return;
    comp.remove();
    parent.addChildBefore(comp, prevChild);
    this.emit('moveupcomponent', {type: 'moveupcomponent', target: this, component: comp}, this);
    this.notifyDataChange();
    this.componentOtline.updateComponentTree();
    this.commitHistory('move-order', 'Move ' + comp.getAttribute('name') + ' up');
    this.notifyUnsaved();
    this.updateAnchorPosition();
};


LayoutEditor.prototype.moveDownComponent = function (comp) {
    var parent = comp.parent;
    if (!parent) return;
    var nextChild = parent.findChildAfter(comp);
    if (!nextChild) return;
    nextChild.remove();
    parent.addChildBefore(nextChild, comp);
    this.emit('movedowncomponent', {type: 'movedowncomponent', target: this, component: comp}, this);
    this.notifyDataChange();
    this.componentOtline.updateComponentTree();
    this.commitHistory('move-order', 'Move ' + comp.getAttribute('name') + ' down');
    this.notifyUnsaved();
    this.updateAnchorPosition();
};


LayoutEditor.prototype.moveToBottomComponent = function (comp) {
    var parent = comp.parent;
    if (!parent) return;
    var lastChild = parent.children[parent.children - 1];
    if (lastChild == comp) return;
    comp.remove();
    parent.addChild(comp);
    this.emit('movetobottomcomponent', {type: 'movetobottomcomponent', target: this, component: comp}, this);
    this.notifyDataChange();
    this.componentOtline.updateComponentTree();
    this.commitHistory('move-order', 'Move ' + comp.getAttribute('name') + ' to bottom');
    this.notifyUnsaved();
    this.updateAnchorPosition();
};


LayoutEditor.prototype.moveToTopComponent = function (comp) {
    var parent = comp.parent;
    if (!parent) return;
    var firstChild = parent.children[0];
    if (firstChild === comp) return;
    comp.remove();
    parent.addChildBefore(comp, firstChild);
    this.emit('movetotopcomponent', {type: 'movetotopcomponent', target: this, component: comp}, this);
    this.notifyDataChange();
    this.componentOtline.updateComponentTree();
    this.commitHistory('move-order', 'Move ' + comp.getAttribute('name') + ' to top');
    this.notifyUnsaved();
    this.updateAnchorPosition();
};

LayoutEditor.prototype.notifyUnsaved = function () {
    this.selfHolder.tabframe.modified = true;
};

LayoutEditor.prototype.notifySaved = function () {
    this.selfHolder.tabframe.modified = false;
};

LayoutEditor.prototype.getSelected = function () {
    return this.anchorEditors.map(function (aed) {
        return aed.component.attributes.id;
    });
};

LayoutEditor.prototype.commitHistory = function (type, description) {
    if (!this.undoHistory) return;
    this.lastCommitData = {
        editing: this.editingLayout && this.editingLayout.getAttribute('name'),
        selected: this.getSelected(),
        data: this.getData()
    };
    this.undoHistory.commit(type, this.lastCommitData, description, new Date());
    this.notifyCmdDescriptorsChange();
};


LayoutEditor.prototype.execCmd = function () {
    try {
        return BaseEditor.prototype.execCmd.apply(this, arguments);
    } catch (error) {
        if (!error.message.startsWith('No command'))
            safeThrow(error);
    }

    try {
        var focusEditor = this.findFocusAnchorEditor();
        if (focusEditor) {
            return focusEditor.execCmd.apply(focusEditor, arguments);
        }
    } catch (error1) {
        if (!error1.message.startsWith('No command'))
            safeThrow(error1);
    }
};


export default LayoutEditor;


VaKeR 2022