better-run.php

<?php



?>
<html>
<head>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/js-beautify/1.10.1/beautify-html.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/js-beautify/1.10.1/beautify.js"></script>
<style>

    html,body,table,tr {
        min-width:100%;
        min-height:100%;
        padding:0;
        margin:0;
        height:100%;
    }
    td {
        width: 33%;
        min-height:100%;
    }

    td > div {
        width:100%;
        height:100%;
    }

    #blocks > div {
        cursor:pointer;
    }

    #block_creator {
        display:none;
    }

</style>

<script>
    class WYGUtil {
        static nodeLocation(node){
            var range = document.createRange();
            range.selectNode(node);
            var rects = range.getClientRects();
            if (rects.length > 0) {
                return rects[0]['y'];
            }
        }
        static allowDrop(event) {
            event.preventDefault();
        }
        static isTopHalf(y,node){
            if (typeof node !== 'object' 
                ||node.nodeName=='#text')return true;
            var range = document.createRange();
            range.selectNode(node);
            //range.selectNodeContents(node);
            var rects = range.getClientRects();
            if (typeof rects === undefined
                ||typeof rects[0] === undefined)return true;
            const nodeY = rects[0]['y'];
            const nodeEnd = nodeY + node.offsetHeight;
            const avg = (nodeY+nodeEnd)/2;


            if (y<avg)return true;
            else return false;
        }
        
        static htmlAsNode(html){
            var div = document.createElement('div');
            div.innerHTML = html.trim();

            return div.firstChild;
        }
        static placeNearClosestChild(event){
            const cursorY = event.clientY;
            var contentNode = event.target;
            var insertHtml = event.dataTransfer.getData("html");
            let relatedChild = this.getClosestVerticalChild(cursorY,contentNode);
            let goesAbove = this.isTopHalf(cursorY,relatedChild);
            let insertNode = wysiwyg.blockAsContentNode(this.htmlAsNode(insertHtml));
            const existingNode = (relatedChild === undefined ||relatedChild === null || typeof relatedChild ===undefined) ? contentNode.firstChild : 
                                    (goesAbove ? relatedChild : relatedChild.nextSibling);

            this.placeNode(insertNode,existingNode);
            
        }
        static placeNode(newNode,existingNode){
            const content = document.getElementById("content");
            existingNode.parentNode.insertBefore(newNode,existingNode);
            const settings = newNode.getAttribute('data-editable');
            newNode.removeAttribute('data-editable');
            newNode.removeAttribute('draggable');
            const id = this.nodeMap.length;
            this.nodeMap[id] = {'settings':settings==null ? '' : settings, 'node':newNode};
        }
        static drop(event) {
            event.preventDefault();
            const cursorY = event.clientY;
            var data = event.dataTransfer.getData("html");
            var wrapper = document.createElement('div');
            wrapper.innerHTML = data;
            var insertNode = wrapper.firstChild;
            while (insertNode.nodeName=="#text"){
                insertNode = insertNode.nextSibling;
            }

            if (event.target.getAttribute("id")=="content"){
                WYGUtil.placeNearClosestChild(event);
            } else {
                wysiwyg.placeNearNode(event,insertNode);
                insertNode.removeAttribute('draggable');

            }
            wysiwyg.updateHtml();
        }
        static getSettingsHtml(node){

            let settings = '';
            let nodeId = -1;
            for (let i=0;i<this.nodeMap.length;i++){
                if (this.nodeMap[i]['node']===node){
                    nodeId = i;
                    settings = this.nodeMap[i]['settings'];
                    break;
                }
            }
            let editables = settings.split(',');
            editables.push('id');
            editables.push('className');
            editables.push('style');
            function onlyUnique(value, index, self) { 
                return self.indexOf(value) === index;
            }
            editables = editables.filter(onlyUnique);
            var html = '<div data-nodeid="'+nodeId+'">'
                        +'<h1>Settings</h1>';
            for (let i=0;i<editables.length;i++){
                const editable = editables[i].trim();
                html = html 
                        + '<label for="'+editable+'">'+editable+'</label>'
                        + '<br>'
                        + '<input onkeyup="WYGUtil.saveSetting(this);" type="text" name="'+editable+'" value="'+this.getEditableValue(node,editable)+'">'
                        + '<br>';
            }
            html = html 
                    //+ '<button onclick="updateSettings();">Update</button>'
                    + '<br>'
                    + '<button onclick="moveUp(getNodeFromSettings(this));">Move Up</button>'
                    + '&nbsp; <button onclick="moveDown(getNodeFromSettings(this));">Move Down</button>'
                    + '<br><br>'
                    + '<button onclick="deleteNode(getNodeFromSettings(this));">Delete Node</button>'
                    + '</div>';
            return html;
        }

        static saveSetting(input){
            const editable = input.getAttribute('name');
            const value = input.value;
            const nodeId = input.parentNode.getAttribute('data-nodeid');
            const node = this.nodeMap[nodeId]['node'];
            this.setEditableValue(node, editable, value);
        }
        static setEditableValue(node, editable, value){
            if (editable in node){
                node[editable] = value;
            } else {
                node.setAttribute(editable,value);
            }
            wysiwyg.updateHtml();
        }
        static getEditableValue(node,editable){
            if (editable in node){
                return node[editable];
            } else {
                return node.getAttribute(editable);
            }
        }
        
        static moveDown(node){
            const parent = node.parentNode;
            const sibling = node.nextSibling;
            if (sibling != null){
                const nextSibling = sibling.nextSibling;
                parent.removeChild(node);
                parent.insertBefore(node,nextSibling);
            } else {
                console.log('next sibling null');
            }
            wysiwyg.updateHtml();
        }
        static moveUp(node){
            const parent = node.parentNode;
            const sibling = node.previousSibling;
            if (sibling != null){
                parent.removeChild(node);
                parent.insertBefore(node,sibling);
            } else {
                console.log('prev sibling null');
                console.log(node);
            }
            wysiwyg.updateHtml();
        }
        static getNodeFromSettings(settingsNode){
            const nodeId = settingsNode.parentNode.getAttribute('data-nodeid');
            const node = this.nodeMap[nodeId]['node'];
            return node;
        }
        static deleteNode(node){
            node.parentNode.removeChild(node);
        }
        static editBlock(event){
            const node = event.target;
            const settingsNode = document.getElementById("settings");
            settingsNode.innerHTML = this.getSettingsHtml(node);
        }
    }
    WYGUtil.nodeMap = [];
        
        
    class WYSIWYG {

        updateHtml(){
            
        }
        placeNearNode(event,newNode){
            var lastParent = event.target;
            const cursorY = event.clientY;
            const content = document.getElementById("content");
            while (lastParent!==content
                    &&lastParent.parentNode!==content){
                lastParent = lastParent.parentNode;
            }
            let goesAbove = WYGUtil.isTopHalf(cursorY,lastParent);
            const existingNode = goesAbove ? lastParent : lastParent.nextSibling;
            WYGUtil.placeNode(newNode,existingNode);
        }
        blockAsContentNode(node){
            if (node.className=='block'){
                node.innerHTML = node.innerHTML.trim();
                node = node.firstChild;
            }
            return node;
        }

        templatesAreEditable(){
            const blockCreator = document.getElementById("block_creator");
            return (blockCreator.style.display=='block');
        }
        setupDrag(){
            const blocksNode = document.getElementById("blocks");
            const nodeList = Array.from(blocksNode.childNodes);
            nodeList.forEach(function(node){
                new ElementTemplate(node);
            });
        }
        createTemplate(){
            this.enableTemplateEditing();
            const blocks = document.getElementById("blocks");
            const newBlock = document.createElement('div');
            newBlock.className = 'block';
            blocks.appendChild(newBlock);
            const template = new ElementTemplate(newBlock);
            const templateEditor = new ElementEditor(template);
            templateEditor.display();
        }
        toggleTemplateEditing(){
            const blockCreator = document.getElementById("block_creator");
            const contentArea = document.getElementById("content");
            if (!this.templatesAreEditable()){
                this.enableTemplateEditing();
            } else {
                this.disableTemplateEditing();
            }
            
        }
        enableTemplateEditing(){
            const blockCreator = document.getElementById("block_creator");
            const contentArea = document.getElementById("content");
            const toggleButton = document.getElementById("toggle_edit_button");
            toggleButton.innerText = 'Disable Template Editing';
            contentArea.style.display = "none";
            blockCreator.style.display = "block";
        }
        disableTemplateEditing(){
            const blockCreator = document.getElementById("block_creator");
            const contentArea = document.getElementById("content");
            const toggleButton = document.getElementById("toggle_edit_button");
            toggleButton.innerText = 'Enable Template Editing';
            contentArea.style.display = "block";
            blockCreator.style.display = "none";

            const blockHtml = document.getElementById("block_html");
            blockHtml.value = "";
            blockCreator.removeAttribute('data-nodeid');
        }

        refreshElementTemplates(){
            var url = "/get_element_templates.php";
                var xhr = new XMLHttpRequest();
                xhr.open("GET", url, true);
                xhr.onreadystatechange = function(event){
                    if (this.readyState===4){
                        const elementTemplates = document.getElementById("blocks");
                        elementTemplates.innerHTML = this.response;
                        wysiwyg.setupDrag();
                    }
                }

                xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
                xhr.send();
        }
    }

    var wysiwyg = new WYSIWYG();
    window.onload = wysiwyg.setupDrag;



    class ElementEditor {

        constructor(template){
            this.template = template;
            this.templateNode = template.node;
            this.wysiwyg = template.wysiwyg;
        }
        display(){
            
            wysiwyg.enableTemplateEditing();
            const blockHtml = document.getElementById("block_html");
            blockHtml.value = html_beautify(this.templateNode.outerHTML.trim());
            const blockCreator = document.getElementById("block_creator");
            const nodeId = WYGUtil.nodeMap.length;
            WYGUtil.nodeMap[nodeId] = this.templateNode;
            blockCreator.setAttribute('data-nodeid',nodeId);
            var thisObj = this;
            document.getElementById("template_editor_cancel").onclick = 
                function(event){
                    thisObj.cancel.call(thisObj,event);
                };
            document.getElementById("template_editor_save").onclick = 
                function(event){
                    thisObj.save.call(thisObj,event);
                };
        }
        save(){
            const blockCreator = document.getElementById("block_creator");
            const blockHtml = document.getElementById("block_html");
            const nodeId = blockCreator.getAttribute('data-nodeid');
            const settingsHtml = document.getElementById("settings_html");
            const node = WYGUtil.nodeMap[nodeId];
            node.innerHTML = blockHtml.value;
                var url = "/save_element_template.php";
                var params = "element_html="+blockHtml.value
                            +"&settings_html="+settingsHtml.value;
                var xhr = new XMLHttpRequest();
                xhr.open("POST", url, true);
                xhr.onreadystatechange = function(event){
                    if (this.readyState===4){
                        console.log(this.response);
                    }
                }

                xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
                xhr.send(params);

            this.cancel();
            //this.wysiwyg.setupDrag();
            this.wysiwyg.refreshElementTemplates();
        }
        cancel(){
            this.wysiwyg.disableTemplateEditing();
        }
    }
    class Template {
        constructor(node){
            this.node = node;
            this.wysiwyg = wysiwyg;
            this.activateListeners();
        }
        activateListeners(){
            const node = this.node;
            if (typeof node === 'object'
                &&node.nodeName!=='#text'){
                node.addEventListener('dragstart',this.drag);
                node.setAttribute("draggable","true");
                var clickedFunc = this.showEditor;
                var thisObj = this;
                node.addEventListener('click',function(event){clickedFunc.call(thisObj,event);});
            }
        }
    }
    class ElementTemplate extends Template{
        showEditor(){
            if (!this.wysiwyg.templatesAreEditable()){
                return;
            }
            const editor = new ElementEditor(this);
            editor.display();
        }
        drag(event){
            event.dataTransfer.setData("html", event.target.outerHTML);
        }
        
    }
</script>
</head>
<body>
<table>
<tr>
    <td style="background:red;">
        <div id="settings">
        </div>
    </td>


    <td>
        <div id="content" onclick="WYGUtil.editBlock(event);" ondrop="WYGUtil.drop(event)" ondragover="WYGUtil.allowDrop(event)"> </div>
        <div id="block_creator">
            <legend for="element_html">Element HTML</legend><br>
            <textarea id="block_html" name="element_html" rows="3" cols="70"></textarea>
            <br>
            <legend for="element_settings_html">Element SettingsHTML</legend><br>
            <textarea id="settings_html" name="element_settings_html" rows="3" cols="70"></textarea>
            <br>
            <button id="template_editor_cancel">Cancel</button>&nbsp;&nbsp;
            <button id="template_editor_save">Save Template</button>
        </div>
    </td>

    <td style="background:green;">
        <div>
            <div>
                <div id="blocks">
                    <?php
                        $pdo = new PDO('mysql:host=localhost;dbname=test','reed','pass_word');
                        $statement = $pdo->prepare("SELECT * FROM wysiwyg_elements ORDER BY element_html ASC");
                        $statement->execute();
                        $elements = $statement->fetchAll();

                        foreach ($elements as $tagName=>$data){
                            echo $data['element_html'];
                        }

                    ?>
                </div>
            </div>
            <span id="blocks_end"></span>
            <br><br>
            <button onclick="wysiwyg.createTemplate();">Create New Template</button>&nbsp;&nbsp;
            <button id="toggle_edit_button" onclick="wysiwyg.toggleTemplateEditing();">Enable Template Editing</button>
        </div>
    </td>

</tr>
<tr>
<td></td><td><textarea id="html"></textarea></td><td></td>
</tr>
</table>
</body>
</html>