<commit_before>(function () {
    var ui = glinamespace("gli.ui");

    var Toolbar = function (w) {
        var self = this;
        var document = w.document;

        this.window = w;
        this.elements = {
            bar: w.root.getElementsByClassName("window-toolbar")[0]
        };
        this.buttons = {};
    };
    Toolbar.prototype.addSelection = function (name, tip) {
        var self = this;

        var el = document.createElement("div");
        el.className = "toolbar-button toolbar-button-enabled toolbar-button-command-" + name;

        el.title = tip;
        el.innerHTML = tip;

        el.onclick = function () {
            self.window.selectTab(name);
        };

        this.elements.bar.appendChild(el);

        this.buttons[name] = el;
    };
    Toolbar.prototype.toggleSelection = function (name) {
        for (var n in this.buttons) {
            var el = this.buttons[n];
            el.className = el.className.replace("toolbar-button-selected", "toolbar-button-enabled");
        }
        var el = this.buttons[name];
        if (el) {
            el.className = el.className.replace("toolbar-button-disabled", "toolbar-button-selected");
            el.className = el.className.replace("toolbar-button-enabled", "toolbar-button-selected");
        }
    };

    function writeDocument(document, elementHost) {
        var root = document.createElement("div");
        root.className = "window";

        // Toolbar
        //

        // ...
        var toolbar = document.createElement("div");
        toolbar.className = "window-toolbar";
        root.appendChild(toolbar);

        // Middle
        //

        // ...
        var middle = document.createElement("div");
        middle.className = "window-middle";
        root.appendChild(middle);

        if (elementHost) {
            elementHost.appendChild(root);
        } else {
            document.body.appendChild(root);
        }

        root.elements = {
            toolbar: toolbar,
            middle: middle
        };

        return root;
    };

    var Tab = function (w, container, name) {
        this.name = name;
        this.hasFocus = false;

        var el = this.el = document.createElement("div");
        el.className = "window-tab-root";
        container.appendChild(el);
    };
    Tab.prototype.gainFocus = function () {
        this.hasFocus = true;
        this.el.className += " window-tab-selected";
    };
    Tab.prototype.loseFocus = function () {
        this.hasFocus = false;
        this.el.className = this.el.className.replace(" window-tab-selected", "");
    };

    // TODO: move these someplace else
    var genericLeftRightView =
        '
' +
        '
' +
        '
' +
        '           ' +
        '
' +
        '
' +
        '
' +
        '
' +
        '            ' +
        '
' +
        '
' +
        '            ??
' +
        '
' +
        '
';

    var TraceTab = function (w) {
        var html =
        '
' +
        '
' +
        '
' +
        '            ' +
        '
' +
        '
' +
        '
' +
        '
' +
        '                    ' +
        '
' +
        '
' +
        '                    ' +
        '
' +
        '
' +
        '
' +
        '
' +
        '
' +
        '
' +
        '            ' +
        '
' +
        '
' +
        '            capture, delete
' +
        '
' +
        '
';
        this.el.innerHTML = html;

        this.listing = new gli.ui.LeftListing(w, this.el, "frame", function (el, frame) {
            var canvas = document.createElement("canvas");
            canvas.className = "gli-reset frame-item-preview";
            canvas.style.cursor = "pointer";
            canvas.width = 80;
            canvas.height = frame.screenshot.height / frame.screenshot.width * 80;

            // Draw the data - hacky, but easiest?
            var ctx2d = canvas.getContext("2d");
            ctx2d.drawImage(frame.screenshot, 0, 0, canvas.width, canvas.height);

            el.appendChild(canvas);

            var number = document.createElement("div");
            number.className = "frame-item-number";
            number.innerHTML = frame.frameNumber;
            el.appendChild(number);
        });
        this.traceView = new gli.ui.TraceView(w, this.el);

        this.listing.valueSelected.addListener(this, function (frame) {
            this.traceView.setFrame(frame);
        });

        var context = w.context;
        for (var n = 0; n < context.frames.length; n++) {
            var frame = context.frames[n];
            this.listing.appendValue(frame);
        }
        if (context.frames.length > 0) {
            this.listing.selectValue(context.frames[context.frames.length - 1]);
        }

        this.layout = function () {
            this.traceView.layout();
        };
    };

    var TimelineTab = function (w) {
    };

    var StateTab = function (w) {
        this.el.innerHTML = genericLeftRightView;
    };

    var TexturesTab = function (w) {
        this.el.innerHTML = genericLeftRightView;

        this.listing = new gli.ui.LeftListing(w, this.el, "texture", function (el, texture) {
            var number = document.createElement("div");
            number.className = "texture-item-number";
            number.innerHTML = texture.id;
            el.appendChild(number);
        });
        this.textureView = new gli.ui.TextureView(w, this.el);

        this.listing.valueSelected.addListener(this, function (texture) {
            this.textureView.setTexture(texture);
        });

        // TODO: append textures already present?
        var context = w.context;
        var textures = context.resources.getTextures();
        for (var n = 0; n < textures.length; n++) {
            var texture = textures[n];
            this.listing.appendValue(texture);
        }
    };

    var BuffersTab = function (w) {
        this.el.innerHTML = genericLeftRightView;

        this.listing = new gli.ui.LeftListing(w, this.el, "buffer", function (el, buffer) {
            var number = document.createElement("div");
            number.className = "buffer-item-number";
            number.innerHTML = buffer.id;
            el.appendChild(number);
        });
        this.bufferView = new gli.ui.BufferView(w, this.el);

        this.listing.valueSelected.addListener(this, function (buffer) {
            this.bufferView.setBuffer(buffer);
        });

        // TODO: append buffers already present?
        var context = w.context;
        var buffers = context.resources.getBuffers();
        for (var n = 0; n < buffers.length; n++) {
            var buffer = buffers[n];
            this.listing.appendValue(buffer);
        }
    };

    var ProgramsTab = function (w) {
        this.el.innerHTML = genericLeftRightView;

        this.listing = new gli.ui.LeftListing(w, this.el, "program", function (el, buffer) {
            var number = document.createElement("div");
            number.className = "program-item-number";
            number.innerHTML = buffer.id;
            el.appendChild(number);
        });
        this.programView = new gli.ui.ProgramView(w, this.el);

        this.listing.valueSelected.addListener(this, function (program) {
            this.programView.setProgram(program);
        });

        // TODO: append programs already present?
        var context = w.context;
        var programs = context.resources.getPrograms();
        for (var n = 0; n < programs.length; n++) {
            var program = programs[n];
            this.listing.appendValue(program);
        }
    };

    var Window = function (context, document, elementHost) {
        var self = this;
        this.context = context;
        this.document = document;

        this.root = writeDocument(document, elementHost);

        this.controller = new gli.replay.Controller();

        this.toolbar = new Toolbar(this);
        this.tabs = {};
        this.currentTab = null;

        var middle = this.root.elements.middle;
        function addTab(name, tip, implType) {
            var tab = new Tab(self, middle, name);

            if (implType) {
                implType.apply(tab, [self]);
            }

            self.toolbar.addSelection(name, tip);

            self.tabs[name] = tab;
        };

        addTab("trace", "Trace", TraceTab);
        addTab("timeline", "Timeline", TimelineTab);
        addTab("state", "State", StateTab);
        addTab("textures", "Textures", TexturesTab);
        addTab("buffers", "Buffers", BuffersTab);
        addTab("programs", "Programs", ProgramsTab);

        this.selectTab("trace");
    };

    Window.prototype.layout = function () {
        for (var n in this.tabs) {
            var tab = this.tabs[n];
            if (tab.layout) {
                tab.layout();
            }
        }
    };

    Window.prototype.selectTab = function (name) {
        if (name.name) {
            name = name.name;
        }
        if (this.currentTab && this.currentTab.name == name) {
            return;
        }
        console.log("switching to tab " + name);
        var tab = this.tabs[name];
        if (!tab) {
            return;
        }

        if (this.currentTab) {
            this.currentTab.loseFocus();
            this.currentTab = null;
        }

        this.currentTab = tab;
        this.currentTab.gainFocus();
        this.toolbar.toggleSelection(name);

        if (tab.layout) {
            tab.layout();
        }
    };

    Window.prototype.appendFrame = function (frame) {
        var tab = this.tabs["trace"];
        this.selectTab(tab);
        tab.listing.appendValue(frame);
        tab.listing.selectValue(frame);
    };

    Window.prototype.showTexture = function (texture) {
        var tab = this.tabs["textures"];
        this.selectTab(tab);
        tab.listing.selectValue(texture);
    };

    Window.prototype.showBuffer = function (buffer) {
        var tab = this.tabs["buffers"];
        this.selectTab(tab);
        tab.listing.selectValue(buffer);
    };

    Window.prototype.showProgram = function (program) {
        var tab = this.tabs["programs"];
        this.selectTab(tab);
        tab.listing.selectValue(program);
    };

    ui.Window = Window;
})();
<commit_msg>better names in the lists<commit_after>(function () {
    var ui = glinamespace("gli.ui");

    var Toolbar = function (w) {
        var self = this;
        var document = w.document;

        this.window = w;
        this.elements = {
            bar: w.root.getElementsByClassName("window-toolbar")[0]
        };
        this.buttons = {};
    };
    Toolbar.prototype.addSelection = function (name, tip) {
        var self = this;

        var el = document.createElement("div");
        el.className = "toolbar-button toolbar-button-enabled toolbar-button-command-" + name;

        el.title = tip;
        el.innerHTML = tip;

        el.onclick = function () {
            self.window.selectTab(name);
        };

        this.elements.bar.appendChild(el);

        this.buttons[name] = el;
    };
    Toolbar.prototype.toggleSelection = function (name) {
        for (var n in this.buttons) {
            var el = this.buttons[n];
            el.className = el.className.replace("toolbar-button-selected", "toolbar-button-enabled");
        }
        var el = this.buttons[name];
        if (el) {
            el.className = el.className.replace("toolbar-button-disabled", "toolbar-button-selected");
            el.className = el.className.replace("toolbar-button-enabled", "toolbar-button-selected");
        }
    };

    function writeDocument(document, elementHost) {
        var root = document.createElement("div");
        root.className = "window";

        // Toolbar
        //

        // ...
        var toolbar = document.createElement("div");
        toolbar.className = "window-toolbar";
        root.appendChild(toolbar);

        // Middle
        //

        // ...
        var middle = document.createElement("div");
        middle.className = "window-middle";
        root.appendChild(middle);

        if (elementHost) {
            elementHost.appendChild(root);
        } else {
            document.body.appendChild(root);
        }

        root.elements = {
            toolbar: toolbar,
            middle: middle
        };

        return root;
    };

    var Tab = function (w, container, name) {
        this.name = name;
        this.hasFocus = false;

        var el = this.el = document.createElement("div");
        el.className = "window-tab-root";
        container.appendChild(el);
    };
    Tab.prototype.gainFocus = function () {
        this.hasFocus = true;
        this.el.className += " window-tab-selected";
    };
    Tab.prototype.loseFocus = function () {
        this.hasFocus = false;
        this.el.className = this.el.className.replace(" window-tab-selected", "");
    };

    // TODO: move these someplace else
    var genericLeftRightView =
        '
' +
        '
' +
        '
' +
        '           ' +
        '
' +
        '
' +
        '
' +
        '
' +
        '            ' +
        '
' +
        '
' +
        '            ??
' +
        '
' +
        '
';

    var TraceTab = function (w) {
        var html =
        '
' +
        '
' +
        '
' +
        '            ' +
        '
' +
        '
' +
        '
' +
        '
' +
        '                    ' +
        '
' +
        '
' +
        '                    ' +
        '
' +
        '
' +
        '
' +
        '
' +
        '
' +
        '
' +
        '            ' +
        '
' +
        '
' +
        '            capture, delete
' +
        '
' +
        '
';
        this.el.innerHTML = html;

        this.listing = new gli.ui.LeftListing(w, this.el, "frame", function (el, frame) {
            var canvas = document.createElement("canvas");
            canvas.className = "gli-reset frame-item-preview";
            canvas.style.cursor = "pointer";
            canvas.width = 80;
            canvas.height = frame.screenshot.height / frame.screenshot.width * 80;

            // Draw the data - hacky, but easiest?
            var ctx2d = canvas.getContext("2d");
            ctx2d.drawImage(frame.screenshot, 0, 0, canvas.width, canvas.height);

            el.appendChild(canvas);

            var number = document.createElement("div");
            number.className = "frame-item-number";
            number.innerHTML = frame.frameNumber;
            el.appendChild(number);
        });
        this.traceView = new gli.ui.TraceView(w, this.el);

        this.listing.valueSelected.addListener(this, function (frame) {
            this.traceView.setFrame(frame);
        });

        var context = w.context;
        for (var n = 0; n < context.frames.length; n++) {
            var frame = context.frames[n];
            this.listing.appendValue(frame);
        }
        if (context.frames.length > 0) {
            this.listing.selectValue(context.frames[context.frames.length - 1]);
        }

        this.layout = function () {
            this.traceView.layout();
        };
    };

    var TimelineTab = function (w) {
    };

    var StateTab = function (w) {
        this.el.innerHTML = genericLeftRightView;
    };

    var TexturesTab = function (w) {
        this.el.innerHTML = genericLeftRightView;

        this.listing = new gli.ui.LeftListing(w, this.el, "texture", function (el, texture) {
            var gl = w.context;

            var number = document.createElement("div");
            number.className = "texture-item-number";
            number.innerHTML = "Texture" + ((texture.type == gl.TEXTURE_2D) ? "2D" : "Cube") + " " + texture.id;
            el.appendChild(number);
        });
        this.textureView = new gli.ui.TextureView(w, this.el);

        this.listing.valueSelected.addListener(this, function (texture) {
            this.textureView.setTexture(texture);
        });

        // TODO: append textures already present?
        var context = w.context;
        var textures = context.resources.getTextures();
        for (var n = 0; n < textures.length; n++) {
            var texture = textures[n];
            this.listing.appendValue(texture);
        }
    };

    var BuffersTab = function (w) {
        this.el.innerHTML = genericLeftRightView;

        this.listing = new gli.ui.LeftListing(w, this.el, "buffer", function (el, buffer) {
            var gl = w.context;

            var number = document.createElement("div");
            number.className = "buffer-item-number";
            number.innerHTML = "Buffer " + buffer.id + ((buffer.type == gl.ELEMENT_ARRAY_BUFFER) ? " /E" : "");
            el.appendChild(number);
        });
        this.bufferView = new gli.ui.BufferView(w, this.el);

        this.listing.valueSelected.addListener(this, function (buffer) {
            this.bufferView.setBuffer(buffer);
        });

        // TODO: append buffers already present?
        var context = w.context;
        var buffers = context.resources.getBuffers();
        for (var n = 0; n < buffers.length; n++) {
            var buffer = buffers[n];
            this.listing.appendValue(buffer);
        }
    };

    var ProgramsTab = function (w) {
        this.el.innerHTML = genericLeftRightView;

        this.listing = new gli.ui.LeftListing(w, this.el, "program", function (el, buffer) {
            var number = document.createElement("div");
            number.className = "program-item-number";
            number.innerHTML = "Program " + buffer.id;
            el.appendChild(number);
        });
        this.programView = new gli.ui.ProgramView(w, this.el);

        this.listing.valueSelected.addListener(this, function (program) {
            this.programView.setProgram(program);
        });

        // TODO: append programs already present?
        var context = w.context;
        var programs = context.resources.getPrograms();
        for (var n = 0; n < programs.length; n++) {
            var program = programs[n];
            this.listing.appendValue(program);
        }
    };

    var Window = function (context, document, elementHost) {
        var self = this;
        this.context = context;
        this.document = document;

        this.root = writeDocument(document, elementHost);

        this.controller = new gli.replay.Controller();

        this.toolbar = new Toolbar(this);
        this.tabs = {};
        this.currentTab = null;

        var middle = this.root.elements.middle;
        function addTab(name, tip, implType) {
            var tab = new Tab(self, middle, name);

            if (implType) {
                implType.apply(tab, [self]);
            }

            self.toolbar.addSelection(name, tip);

            self.tabs[name] = tab;
        };

        addTab("trace", "Trace", TraceTab);
        addTab("timeline", "Timeline", TimelineTab);
        addTab("state", "State", StateTab);
        addTab("textures", "Textures", TexturesTab);
        addTab("buffers", "Buffers", BuffersTab);
        addTab("programs", "Programs", ProgramsTab);

        this.selectTab("trace");
    };

    Window.prototype.layout = function () {
        for (var n in this.tabs) {
            var tab = this.tabs[n];
            if (tab.layout) {
                tab.layout();
            }
        }
    };

    Window.prototype.selectTab = function (name) {
        if (name.name) {
            name = name.name;
        }
        if (this.currentTab && this.currentTab.name == name) {
            return;
        }
        console.log("switching to tab " + name);
        var tab = this.tabs[name];
        if (!tab) {
            return;
        }

        if (this.currentTab) {
            this.currentTab.loseFocus();
            this.currentTab = null;
        }

        this.currentTab = tab;
        this.currentTab.gainFocus();
        this.toolbar.toggleSelection(name);

        if (tab.layout) {
            tab.layout();
        }
    };

    Window.prototype.appendFrame = function (frame) {
        var tab = this.tabs["trace"];
        this.selectTab(tab);
        tab.listing.appendValue(frame);
        tab.listing.selectValue(frame);
    };

    Window.prototype.showTexture = function (texture) {
        var tab = this.tabs["textures"];
        this.selectTab(tab);
        tab.listing.selectValue(texture);
    };

    Window.prototype.showBuffer = function (buffer) {
        var tab = this.tabs["buffers"];
        this.selectTab(tab);
        tab.listing.selectValue(buffer);
    };

    Window.prototype.showProgram = function (program) {
        var tab = this.tabs["programs"];
        this.selectTab(tab);
        tab.listing.selectValue(program);
    };

    ui.Window = Window;
})();
<|endoftext|>


<commit_before>// Licensed under the Apache License, Version 2.0 (the “License”); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an “AS IS” BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.

import {html as beautifyHtml} from 'js-beautify'
import {minify as minifyHtml} from 'html-minifier'
import {serialize} from 'parse5'

const HTML5_NAMESPACE = 'http://www.w3.org/1999/xhtml'

/**
 * Creates a Parse5 node.
 */
export function createNode({tagName, ...other}) {
  return {
    attrs: [],
    childNodes: [],
    namespaceURI: HTML5_NAMESPACE,
    nodeName: tagName,
    tagName: tagName,
    ...other,
  }
}

/**
 * Extract an attribute’s value from the node; Returns undefined if the
 * attribute is not found.
 */
export function getAttributeValue(node, attributeName) {
  for (let attribute of node.attrs) {
    if (attribute.name === attributeName)
      return attribute.value
  }
  return undefined
}

/**
 * Returns the node by its tag name.
 */
export function getNodeByTagName(tree, tagName) {
  for (let node of walk(tree)) {
    if (node.tagName === tagName)
      return node
  }
  return undefined
}

/**
 * Returns a resource request.
 */
export function getResourceRequest(node) {
  let {tagName} = node
  if (!tagName)
    return undefined

  switch (tagName) {
    case 'link':
      return getAttributeValue(node, 'href')
    case 'img':
      return getAttributeValue(node, 'src')
  }

  return undefined
}

/**
 * Returns a formatted HTML string.
 */
export function minifiedHtmlString(tree) {
  let serialized = serialize(tree)
  // https://github.com/kangax/html-minifier
  return minifyHtml(serialized, {
    caseSensitive: false,
    collapseBooleanAttributes: true,
    collapseInlineTagWhitespace: true,
    collapseWhitespace: true,
    conservativeCollapse: false,
    decodeEntities: true,
    html5: true,
    includeAutoGeneratedTags: false,
    keepClosingSlash: false,
    minifyCSS: true,
    minifyJS: true,
    preserveLineBreaks: false,
    preventAttributesEscaping: true,
    processConditionalComments: false,
    quoteCharacter: '"',
    removeAttributeQuotes: true,
    removeComments: true,
    removeEmptyAttributes: true,
    removeEmptyElements: false,
    removeOptionalTags: true,
    removeRedundantAttributes: true,
    removeScriptTypeAttributes: true,
    removeStyleLinkTypeAttributes: true,
    sortAttributes: true,
    sortClassName: true,
    useShortDoctype: true,
  })
}

/**
 * Returns a formatted HTML string.
 */
export function prettifiedHtmlString(tree) {
  let serialized = serialize(tree)
  // https://github.com/beautify-web/js-beautify
  return beautifyHtml(serialized, {
    indent_char: ' ',
    max_preserve_newlines: 0,
    indent_inner_html: true,
    indent_size: 2,
    sep: '\n',
    unformatted: ['code', 'pre'],
  })
}

/**
 * Update a node’s attribute value.
 */
export function setAttributeValue(node, attributeName, value) {
  for (let attribute of node.attrs) {
    if (attribute.name === attributeName)
      attribute.value = value
  }
}

/**
 * Change the resource request.
 */
export function setResourceRequest(node, newRequest) {
  let {tagName} = node
  if (!tagName)
    return

  switch (tagName) {
    case 'link':
      setAttributeValue(node, 'href', newRequest)
      break
    case 'img':
      setAttributeValue(node, 'src', newRequest)
      break
  }
}

/**
 * Recursively walks the parsed tree. It should work in 99.9% of the cases but
 * it needs to be replaced with a non recursive version.
 */
export function * walk(node) {
  yield node

  if (!node.childNodes)
    return

  for (let child of node.childNodes)
    yield * walk(child)
}
<commit_msg>Fix parse5 attribute helpers<commit_after>// Licensed under the Apache License, Version 2.0 (the “License”); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an “AS IS” BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.

import {html as beautifyHtml} from 'js-beautify'
import {minify as minifyHtml} from 'html-minifier'
import {serialize} from 'parse5'

const HTML5_NAMESPACE = 'http://www.w3.org/1999/xhtml'

/**
 * Creates a Parse5 node.
 */
export function createNode({tagName, ...other}) {
  return {
    attrs: [],
    childNodes: [],
    namespaceURI: HTML5_NAMESPACE,
    nodeName: tagName,
    tagName: tagName,
    ...other,
  }
}

/**
 * Extract an attribute’s value from the node; Returns undefined if the
 * attribute is not found.
 */
export function getAttributeValue(node, attributeName) {
  node.attrs ??= []
  for (let attribute of node.attrs) {
    if (attribute.name === attributeName)
      return attribute.value
  }
  return undefined
}

/**
 * Returns the node by its tag name.
 */
export function getNodeByTagName(tree, tagName) {
  for (let node of walk(tree)) {
    if (node.tagName === tagName)
      return node
  }
  return undefined
}

/**
 * Returns a resource request.
 */
export function getResourceRequest(node) {
  let {tagName} = node
  if (!tagName)
    return undefined

  switch (tagName) {
    case 'link':
      return getAttributeValue(node, 'href')
    case 'img':
      return getAttributeValue(node, 'src')
  }

  return undefined
}

/**
 * Returns a formatted HTML string.
 */
export function minifiedHtmlString(tree) {
  let serialized = serialize(tree)
  // https://github.com/kangax/html-minifier
  return minifyHtml(serialized, {
    caseSensitive: false,
    collapseBooleanAttributes: true,
    collapseInlineTagWhitespace: true,
    collapseWhitespace: true,
    conservativeCollapse: false,
    decodeEntities: true,
    html5: true,
    includeAutoGeneratedTags: false,
    keepClosingSlash: false,
    minifyCSS: true,
    minifyJS: true,
    preserveLineBreaks: false,
    preventAttributesEscaping: true,
    processConditionalComments: false,
    quoteCharacter: '"',
    removeAttributeQuotes: true,
    removeComments: true,
    removeEmptyAttributes: true,
    removeEmptyElements: false,
    removeOptionalTags: true,
    removeRedundantAttributes: true,
    removeScriptTypeAttributes: true,
    removeStyleLinkTypeAttributes: true,
    sortAttributes: true,
    sortClassName: true,
    useShortDoctype: true,
  })
}

/**
 * Returns a formatted HTML string.
 */
export function prettifiedHtmlString(tree) {
  let serialized = serialize(tree)
  // https://github.com/beautify-web/js-beautify
  return beautifyHtml(serialized, {
    indent_char: ' ',
    max_preserve_newlines: 0,
    indent_inner_html: true,
    indent_size: 2,
    sep: '\n',
    unformatted: ['code', 'pre'],
  })
}

/**
 * Update a node’s attribute value.
 */
export function setAttributeValue(node, name, value) {
  node.attrs ??= []
  let changed = false

  for (let attribute of node.attrs) {
    if (attribute.name !== name)
      continue
    attribute.value = value
    changed = true
  }

  if (!changed)
    node.attrs.push({name, value})
}

/**
 * Change the resource request.
 */
export function setResourceRequest(node, newRequest) {
  let {tagName} = node
  if (!tagName)
    return

  switch (tagName) {
    case 'link':
      setAttributeValue(node, 'href', newRequest)
      break
    case 'img':
      setAttributeValue(node, 'src', newRequest)
      break
  }
}

/**
 * Recursively walks the parsed tree. It should work in 99.9% of the cases but
 * it needs to be replaced with a non recursive version.
 */
export function * walk(node) {
  yield node

  if (!node.childNodes)
    return

  for (let child of node.childNodes)
    yield * walk(child)
}
<|endoftext|>


<commit_before>
/* eslint no-console: 0 */

const hooks = require('feathers-hooks');
const auth = require('feathers-authentication').hooks;
const verifyHooks = require('../../../hooks').verifyResetHooks; // NEW

exports.before = {
  all: [],
  find: [
    auth.verifyToken(),
    auth.populateUser(),
    auth.restrictToAuthenticated(),
  ],
  get: [
    auth.verifyToken(),
    auth.populateUser(),
    auth.restrictToAuthenticated(),
    auth.restrictToOwner({ ownerField: '_id' }),
  ],
  create: [
    auth.hashPassword(),
    verifyHooks.addVerification(), // set email addr verification info
  ],
  update: [
    auth.verifyToken(),
    auth.populateUser(),
    auth.restrictToAuthenticated(),
    auth.restrictToOwner({ ownerField: '_id' }),
  ],
  patch: [
    auth.verifyToken(),
    auth.populateUser(),
    auth.restrictToAuthenticated(),
    auth.restrictToOwner({ ownerField: '_id' }),
  ],
  remove: [
    auth.verifyToken(),
    auth.populateUser(),
    auth.restrictToAuthenticated(),
    auth.restrictToOwner({ ownerField: '_id' }),
  ],
};

exports.after = {
  find: [
    hooks.remove('password'),
    verifyHooks.removeVerification(true), // return tokens for test scaffold
  ],
  get: [
    hooks.remove('password'),
    verifyHooks.removeVerification(true), // return tokens for test scaffold
  ],
  create: [
    hooks.remove('password'),
    userNotificationCreate, // send notification to verify the email addr
    verifyHooks.removeVerification(true), // return tokens for test scaffold
  ],
  update: [
    hooks.remove('password'),
    verifyHooks.removeVerification(true), // return tokens for test scaffold
  ],
  patch: [
    hooks.remove('password'),
    verifyHooks.removeVerification(),
  ],
  remove: [
    hooks.remove('password'),
    verifyHooks.removeVerification(),
  ],
};

function userNotificationCreate(hook, next) {
  // Data from the UI may be used to determine the transport for notifying the user.
  const user = hook.result;
  const slugForUrl = {
    socketio: 'socket',
    rest: 'rest',
  }[hook.params.provider];

  console.log('-- Sending notification to verify new user\'s email addr');
  console.log(`Dear ${user.username}, please click this link to verify your email addr.`);
  console.log(`  http://localhost:3030/${slugForUrl}/verify/${user.verifyToken}`);

  next(null, hook);
}
<commit_msg>udpdate example to properly require hooks instead of verifyResetHooks<commit_after>
/* eslint no-console: 0 */

const hooks = require('feathers-hooks');
const auth = require('feathers-authentication').hooks;
const verifyHooks = require('../../../hooks').hooks;

exports.before = {
  all: [],
  find: [
    auth.verifyToken(),
    auth.populateUser(),
    auth.restrictToAuthenticated(),
  ],
  get: [
    auth.verifyToken(),
    auth.populateUser(),
    auth.restrictToAuthenticated(),
    auth.restrictToOwner({ ownerField: '_id' }),
  ],
  create: [
    auth.hashPassword(),
    verifyHooks.addVerification(), // set email addr verification info
  ],
  update: [
    auth.verifyToken(),
    auth.populateUser(),
    auth.restrictToAuthenticated(),
    auth.restrictToOwner({ ownerField: '_id' }),
  ],
  patch: [
    auth.verifyToken(),
    auth.populateUser(),
    auth.restrictToAuthenticated(),
    auth.restrictToOwner({ ownerField: '_id' }),
  ],
  remove: [
    auth.verifyToken(),
    auth.populateUser(),
    auth.restrictToAuthenticated(),
    auth.restrictToOwner({ ownerField: '_id' }),
  ],
};

exports.after = {
  find: [
    hooks.remove('password'),
    verifyHooks.removeVerification(true), // return tokens for test scaffold
  ],
  get: [
    hooks.remove('password'),
    verifyHooks.removeVerification(true), // return tokens for test scaffold
  ],
  create: [
    hooks.remove('password'),
    userNotificationCreate, // send notification to verify the email addr
    verifyHooks.removeVerification(true), // return tokens for test scaffold
  ],
  update: [
    hooks.remove('password'),
    verifyHooks.removeVerification(true), // return tokens for test scaffold
  ],
  patch: [
    hooks.remove('password'),
    verifyHooks.removeVerification(),
  ],
  remove: [
    hooks.remove('password'),
    verifyHooks.removeVerification(),
  ],
};

function userNotificationCreate(hook, next) {
  // Data from the UI may be used to determine the transport for notifying the user.
  const user = hook.result;
  const slugForUrl = {
    socketio: 'socket',
    rest: 'rest',
  }[hook.params.provider];

  console.log('-- Sending notification to verify new user\'s email addr');
  console.log(`Dear ${user.username}, please click this link to verify your email addr.`);
  console.log(`  http://localhost:3030/${slugForUrl}/verify/${user.verifyToken}`);

  next(null, hook);
}
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2013 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

/**
 * @fileoverview A div that floats on top of Blockly.  This singleton contains
 *     temporary HTML UI widgets that the user is currently interacting with.
 *     E.g. text input areas, colour pickers, context menus.
 * @author fraser@google.com (Neil Fraser)
 */
'use strict';

/**
 * @name Blockly.WidgetDiv
 * @namespace
 */
goog.module('Blockly.WidgetDiv');

/* eslint-disable-next-line no-unused-vars */
const Rect = goog.requireType('Blockly.utils.Rect');
/* eslint-disable-next-line no-unused-vars */
const Size = goog.requireType('Blockly.utils.Size');
/* eslint-disable-next-line no-unused-vars */
const WorkspaceSvg = goog.requireType('Blockly.WorkspaceSvg');
const common = goog.require('Blockly.common');
const deprecation = goog.require('Blockly.utils.deprecation');
const dom = goog.require('Blockly.utils.dom');

/**
 * The object currently using this container.
 * @type {Object}
 */
let owner = null;

/**
 * Optional cleanup function set by whichever object uses the widget.
 * @type {Function}
 */
let dispose = null;

/**
 * A class name representing the current owner's workspace renderer.
 * @type {string}
 */
let rendererClassName = '';

/**
 * A class name representing the current owner's workspace theme.
 * @type {string}
 */
let themeClassName = '';

/**
 * The HTML container for popup overlays (e.g. editor widgets).
 * @type {?Element}
 * @alias Blockly.WidgetDiv.DIV
 */
let DIV;
/** @deprecated September 2021 */
exports.DIV = DIV;

/**
 * Returns the HTML container for editor widgets.
 * @return {?Element} The editor widget container.
 * @alias Blockly.WidgetDiv.getDiv
 */
const getDiv = function() {
  return DIV;
};
exports.getDiv = getDiv;

/**
 * Allows unit tests to reset the div.
 * @param {?Element} newDiv The new value for the DIV field.
 */
const testOnly_setDiv = function(newDiv) {
  DIV = newDiv;
};
exports.testOnly_setDiv = testOnly_setDiv;

Object.defineProperties(exports, {
  DIV: {
    get: function() {
      deprecation.warn(
        'Blockly.WidgetDiv.DIV', 'September 2021', 'September 2022',
        'Blockly.WidgetDiv.getDiv()');
      return getDiv();
    }
  }
});

/**
 * Create the widget div and inject it onto the page.
 * @alias Blockly.WidgetDiv.createDom
 */
const createDom = function() {
  if (DIV) {
    return;  // Already created.
  }

  DIV = document.createElement('div');
  DIV.className = 'blocklyWidgetDiv';
  const container = common.getParentContainer() || document.body;
  container.appendChild(DIV);
};
exports.createDom = createDom;

/**
 * Initialize and display the widget div.  Close the old one if needed.
 * @param {!Object} newOwner The object that will be using this container.
 * @param {boolean} rtl Right-to-left (true) or left-to-right (false).
 * @param {Function} newDispose Optional cleanup function to be run when the
 *     widget is closed.
 * @alias Blocky.WidgetDiv.show
 */
const show = function(newOwner, rtl, newDispose) {
  hide();
  owner = newOwner;
  dispose = newDispose;
  const div = DIV;
  div.style.direction = rtl ? 'rtl' : 'ltr';
  div.style.display = 'block';
  const mainWorkspace =
      /** @type {!WorkspaceSvg} */ (common.getMainWorkspace());
  rendererClassName = mainWorkspace.getRenderer().getClassName();
  themeClassName = mainWorkspace.getTheme().getClassName();
  dom.addClass(div, rendererClassName);
  dom.addClass(div, themeClassName);
};
exports.show = show;

/**
 * Destroy the widget and hide the div.
 * @alias Blockly.WidgetDiv.hide
 */
const hide = function() {
  if (!isVisible()) {
    return;
  }
  owner = null;

  const div = DIV;
  div.style.display = 'none';
  div.style.left = '';
  div.style.top = '';
  dispose && dispose();
  dispose = null;
  div.textContent = '';

  if (rendererClassName) {
    dom.removeClass(div, rendererClassName);
    rendererClassName = '';
  }
  if (themeClassName) {
    dom.removeClass(div, themeClassName);
    themeClassName = '';
  }
  (/** @type {!WorkspaceSvg} */ (common.getMainWorkspace())).markFocused();
};
exports.hide = hide;

/**
 * Is the container visible?
 * @return {boolean} True if visible.
 * @alias Blockly.WidgetDiv.isVisible
 */
const isVisible = function() {
  return !!owner;
};
exports.isVisible = isVisible;

/**
 * Destroy the widget and hide the div if it is being used by the specified
 * object.
 * @param {!Object} oldOwner The object that was using this container.
 * @alias Blockly.WidgetDiv.hideIfOwner
 */
const hideIfOwner = function(oldOwner) {
  if (owner == oldOwner) {
    hide();
  }
};
exports.hideIfOwner = hideIfOwner;

/**
 * Set the widget div's position and height.  This function does nothing clever:
 * it will not ensure that your widget div ends up in the visible window.
 * @param {number} x Horizontal location (window coordinates, not body).
 * @param {number} y Vertical location (window coordinates, not body).
 * @param {number} height The height of the widget div (pixels).
 */
const positionInternal = function(x, y, height) {
  DIV.style.left = x + 'px';
  DIV.style.top = y + 'px';
  DIV.style.height = height + 'px';
};

/**
 * Position the widget div based on an anchor rectangle.
 * The widget should be placed adjacent to but not overlapping the anchor
 * rectangle.  The preferred position is directly below and aligned to the left
 * (LTR) or right (RTL) side of the anchor.
 * @param {!Rect} viewportBBox The bounding rectangle of the
 *     current viewport, in window coordinates.
 * @param {!Rect} anchorBBox The bounding rectangle of the anchor,
 *     in window coordinates.
 * @param {!Size} widgetSize The size of the widget that is inside
 *     the widget div, in window coordinates.
 * @param {boolean} rtl Whether the workspace is in RTL mode.  This determines
 *     horizontal alignment.
 * @alias Blockly.WidgetDiv.positionWithAnchor
 */
const positionWithAnchor = function(viewportBBox, anchorBBox, widgetSize, rtl) {
  const y = calculateY(viewportBBox, anchorBBox, widgetSize);
  const x = calculateX(viewportBBox, anchorBBox, widgetSize, rtl);

  if (y < 0) {
    positionInternal(x, 0, widgetSize.height + y);
  } else {
    positionInternal(x, y, widgetSize.height);
  }
};
/** @package */
exports.positionWithAnchor = positionWithAnchor;

/**
 * Calculate an x position (in window coordinates) such that the widget will not
 * be offscreen on the right or left.
 * @param {!Rect} viewportBBox The bounding rectangle of the
 *     current viewport, in window coordinates.
 * @param {!Rect} anchorBBox The bounding rectangle of the anchor,
 *     in window coordinates.
 * @param {!Size} widgetSize The dimensions of the widget inside
 *     the widget div.
 * @param {boolean} rtl Whether the Blockly workspace is in RTL mode.
 * @return {number} A valid x-coordinate for the top left corner of the widget
 *     div, in window coordinates.
 */
const calculateX = function(viewportBBox, anchorBBox, widgetSize, rtl) {
  if (rtl) {
    // Try to align the right side of the field and the right side of widget.
    const widgetLeft = anchorBBox.right - widgetSize.width;
    // Don't go offscreen left.
    const x = Math.max(widgetLeft, viewportBBox.left);
    // But really don't go offscreen right:
    return Math.min(x, viewportBBox.right - widgetSize.width);
  } else {
    // Try to align the left side of the field and the left side of widget.
    // Don't go offscreen right.
    const x = Math.min(anchorBBox.left, viewportBBox.right - widgetSize.width);
    // But left is more important, because that's where the text is.
    return Math.max(x, viewportBBox.left);
  }
};

/**
 * Calculate a y position (in window coordinates) such that the widget will not
 * be offscreen on the top or bottom.
 * @param {!Rect} viewportBBox The bounding rectangle of the
 *     current viewport, in window coordinates.
 * @param {!Rect} anchorBBox The bounding rectangle of the anchor,
 *     in window coordinates.
 * @param {!Size} widgetSize The dimensions of the widget inside
 *     the widget div.
 * @return {number} A valid y-coordinate for the top left corner of the widget
 *     div, in window coordinates.
 */
const calculateY = function(viewportBBox, anchorBBox, widgetSize) {
  // Flip the widget vertically if off the bottom.
  if (anchorBBox.bottom + widgetSize.height >= viewportBBox.bottom) {
    // The bottom of the widget is at the top of the field.
    return anchorBBox.top - widgetSize.height;
    // The widget could go off the top of the window, but it would also go off
    // the bottom.  The window is just too small.
  } else {
    // The top of the widget is at the bottom of the field.
    return anchorBBox.bottom;
  }
};
<commit_msg>Fix typo in alias<commit_after>/**
 * @license
 * Copyright 2013 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

/**
 * @fileoverview A div that floats on top of Blockly.  This singleton contains
 *     temporary HTML UI widgets that the user is currently interacting with.
 *     E.g. text input areas, colour pickers, context menus.
 * @author fraser@google.com (Neil Fraser)
 */
'use strict';

/**
 * @name Blockly.WidgetDiv
 * @namespace
 */
goog.module('Blockly.WidgetDiv');

/* eslint-disable-next-line no-unused-vars */
const Rect = goog.requireType('Blockly.utils.Rect');
/* eslint-disable-next-line no-unused-vars */
const Size = goog.requireType('Blockly.utils.Size');
/* eslint-disable-next-line no-unused-vars */
const WorkspaceSvg = goog.requireType('Blockly.WorkspaceSvg');
const common = goog.require('Blockly.common');
const deprecation = goog.require('Blockly.utils.deprecation');
const dom = goog.require('Blockly.utils.dom');

/**
 * The object currently using this container.
 * @type {Object}
 */
let owner = null;

/**
 * Optional cleanup function set by whichever object uses the widget.
 * @type {Function}
 */
let dispose = null;

/**
 * A class name representing the current owner's workspace renderer.
 * @type {string}
 */
let rendererClassName = '';

/**
 * A class name representing the current owner's workspace theme.
 * @type {string}
 */
let themeClassName = '';

/**
 * The HTML container for popup overlays (e.g. editor widgets).
 * @type {?Element}
 * @alias Blockly.WidgetDiv.DIV
 */
let DIV;
/** @deprecated September 2021 */
exports.DIV = DIV;

/**
 * Returns the HTML container for editor widgets.
 * @return {?Element} The editor widget container.
 * @alias Blockly.WidgetDiv.getDiv
 */
const getDiv = function() {
  return DIV;
};
exports.getDiv = getDiv;

/**
 * Allows unit tests to reset the div.
 * @param {?Element} newDiv The new value for the DIV field.
 */
const testOnly_setDiv = function(newDiv) {
  DIV = newDiv;
};
exports.testOnly_setDiv = testOnly_setDiv;

Object.defineProperties(exports, {
  DIV: {
    get: function() {
      deprecation.warn(
        'Blockly.WidgetDiv.DIV', 'September 2021', 'September 2022',
        'Blockly.WidgetDiv.getDiv()');
      return getDiv();
    }
  }
});

/**
 * Create the widget div and inject it onto the page.
 * @alias Blockly.WidgetDiv.createDom
 */
const createDom = function() {
  if (DIV) {
    return;  // Already created.
  }

  DIV = document.createElement('div');
  DIV.className = 'blocklyWidgetDiv';
  const container = common.getParentContainer() || document.body;
  container.appendChild(DIV);
};
exports.createDom = createDom;

/**
 * Initialize and display the widget div.  Close the old one if needed.
 * @param {!Object} newOwner The object that will be using this container.
 * @param {boolean} rtl Right-to-left (true) or left-to-right (false).
 * @param {Function} newDispose Optional cleanup function to be run when the
 *     widget is closed.
 * @alias Blockly.WidgetDiv.show
 */
const show = function(newOwner, rtl, newDispose) {
  hide();
  owner = newOwner;
  dispose = newDispose;
  const div = DIV;
  div.style.direction = rtl ? 'rtl' : 'ltr';
  div.style.display = 'block';
  const mainWorkspace =
      /** @type {!WorkspaceSvg} */ (common.getMainWorkspace());
  rendererClassName = mainWorkspace.getRenderer().getClassName();
  themeClassName = mainWorkspace.getTheme().getClassName();
  dom.addClass(div, rendererClassName);
  dom.addClass(div, themeClassName);
};
exports.show = show;

/**
 * Destroy the widget and hide the div.
 * @alias Blockly.WidgetDiv.hide
 */
const hide = function() {
  if (!isVisible()) {
    return;
  }
  owner = null;

  const div = DIV;
  div.style.display = 'none';
  div.style.left = '';
  div.style.top = '';
  dispose && dispose();
  dispose = null;
  div.textContent = '';

  if (rendererClassName) {
    dom.removeClass(div, rendererClassName);
    rendererClassName = '';
  }
  if (themeClassName) {
    dom.removeClass(div, themeClassName);
    themeClassName = '';
  }
  (/** @type {!WorkspaceSvg} */ (common.getMainWorkspace())).markFocused();
};
exports.hide = hide;

/**
 * Is the container visible?
 * @return {boolean} True if visible.
 * @alias Blockly.WidgetDiv.isVisible
 */
const isVisible = function() {
  return !!owner;
};
exports.isVisible = isVisible;

/**
 * Destroy the widget and hide the div if it is being used by the specified
 * object.
 * @param {!Object} oldOwner The object that was using this container.
 * @alias Blockly.WidgetDiv.hideIfOwner
 */
const hideIfOwner = function(oldOwner) {
  if (owner == oldOwner) {
    hide();
  }
};
exports.hideIfOwner = hideIfOwner;

/**
 * Set the widget div's position and height.  This function does nothing clever:
 * it will not ensure that your widget div ends up in the visible window.
 * @param {number} x Horizontal location (window coordinates, not body).
 * @param {number} y Vertical location (window coordinates, not body).
 * @param {number} height The height of the widget div (pixels).
 */
const positionInternal = function(x, y, height) {
  DIV.style.left = x + 'px';
  DIV.style.top = y + 'px';
  DIV.style.height = height + 'px';
};

/**
 * Position the widget div based on an anchor rectangle.
 * The widget should be placed adjacent to but not overlapping the anchor
 * rectangle.  The preferred position is directly below and aligned to the left
 * (LTR) or right (RTL) side of the anchor.
 * @param {!Rect} viewportBBox The bounding rectangle of the
 *     current viewport, in window coordinates.
 * @param {!Rect} anchorBBox The bounding rectangle of the anchor,
 *     in window coordinates.
 * @param {!Size} widgetSize The size of the widget that is inside
 *     the widget div, in window coordinates.
 * @param {boolean} rtl Whether the workspace is in RTL mode.  This determines
 *     horizontal alignment.
 * @alias Blockly.WidgetDiv.positionWithAnchor
 */
const positionWithAnchor = function(viewportBBox, anchorBBox, widgetSize, rtl) {
  const y = calculateY(viewportBBox, anchorBBox, widgetSize);
  const x = calculateX(viewportBBox, anchorBBox, widgetSize, rtl);

  if (y < 0) {
    positionInternal(x, 0, widgetSize.height + y);
  } else {
    positionInternal(x, y, widgetSize.height);
  }
};
/** @package */
exports.positionWithAnchor = positionWithAnchor;

/**
 * Calculate an x position (in window coordinates) such that the widget will not
 * be offscreen on the right or left.
 * @param {!Rect} viewportBBox The bounding rectangle of the
 *     current viewport, in window coordinates.
 * @param {!Rect} anchorBBox The bounding rectangle of the anchor,
 *     in window coordinates.
 * @param {!Size} widgetSize The dimensions of the widget inside
 *     the widget div.
 * @param {boolean} rtl Whether the Blockly workspace is in RTL mode.
 * @return {number} A valid x-coordinate for the top left corner of the widget
 *     div, in window coordinates.
 */
const calculateX = function(viewportBBox, anchorBBox, widgetSize, rtl) {
  if (rtl) {
    // Try to align the right side of the field and the right side of widget.
    const widgetLeft = anchorBBox.right - widgetSize.width;
    // Don't go offscreen left.
    const x = Math.max(widgetLeft, viewportBBox.left);
    // But really don't go offscreen right:
    return Math.min(x, viewportBBox.right - widgetSize.width);
  } else {
    // Try to align the left side of the field and the left side of widget.
    // Don't go offscreen right.
    const x = Math.min(anchorBBox.left, viewportBBox.right - widgetSize.width);
    // But left is more important, because that's where the text is.
    return Math.max(x, viewportBBox.left);
  }
};

/**
 * Calculate a y position (in window coordinates) such that the widget will not
 * be offscreen on the top or bottom.
 * @param {!Rect} viewportBBox The bounding rectangle of the
 *     current viewport, in window coordinates.
 * @param {!Rect} anchorBBox The bounding rectangle of the anchor,
 *     in window coordinates.
 * @param {!Size} widgetSize The dimensions of the widget inside
 *     the widget div.
 * @return {number} A valid y-coordinate for the top left corner of the widget
 *     div, in window coordinates.
 */
const calculateY = function(viewportBBox, anchorBBox, widgetSize) {
  // Flip the widget vertically if off the bottom.
  if (anchorBBox.bottom + widgetSize.height >= viewportBBox.bottom) {
    // The bottom of the widget is at the top of the field.
    return anchorBBox.top - widgetSize.height;
    // The widget could go off the top of the window, but it would also go off
    // the bottom.  The window is just too small.
  } else {
    // The top of the widget is at the bottom of the field.
    return anchorBBox.bottom;
  }
};
<|endoftext|>


<commit_before>define([
	'../../moduleDef',
	'jquery',
	'angular',
	'text!./structuredFilter.tpl.html',
	'text!./structuredFilterNode.tpl.html',
	'text!./booleanValueEditor.tpl.html',
	'text!./datetimeValueEditor.tpl.html',
	'text!./dateValueEditor.tpl.html',
	'text!./enumValueEditor.tpl.html',
	'text!./textValueEditor.tpl.html'
], function (module, $, angular, tpl, nodeTpl, booleanEditorTpl, datetimeEditorTpl,	dateEditorTpl, enumEditorTpl, textEditorTpl) {
	var editorTpls = { };

	module.directive('structuredFilter', ['$compile', 'helpers', 'filteringService', 'metadataService',
		function($compile, $helpers, $filteringService, $metadataService) {
			angular.extend(editorTpls, {
				boolean: $compile(booleanEditorTpl),
				datetime: $compile(datetimeEditorTpl),
				date: $compile(dateEditorTpl),
				enum: $compile(enumEditorTpl),
				text: $compile(textEditorTpl)
			});

			return {
				replace: true,
				template: tpl,
				scope: {
					rootNode: '=filterNgModel',
					meta: '='
				},
				controller: ['$scope', '$rootScope', function($scope, $rootScope) {
					angular.extend($scope, {
						i18n: $rootScope.i18n,

						getMetadata: function(callback, forceRefresh) {
							if(forceRefresh || !$scope.metadata) {
								$metadataService.modelMetadata($scope.meta, null, function(metadata) {
									$scope.metadata = metadata;
									callback($scope.metadata);
								});
								return;
							}
							callback($scope.metadata);
						},

						addNode: function() {
							$scope.$broadcast('addRootNode');
						},

						clear: function() {
							$scope.$broadcast('clear');
						}
					});

					$scope.$watch('rootNode', function(value) {
						if(value) {
							value.op = '&&';
							delete value.path;
							delete value.value;
							delete value.not;
							if(!value.items) {
								value.items = [];
							}
						}
						else {
							$scope.rootNode = { op: '&&', items: [ ] };
							$scope.clear();
						}
					}, true);
				}]
			};
		}
	])
	.directive('structuredFilterNode', ['helpers', 'filteringService', 'metadataService', '$compile', '$filter',
		function($helpers, $filteringService, $metadataService, $compile, $filter) {
			var compiledNodeTpl = $compile(nodeTpl);

			return {
				replace: true,
				scope: {
					node: '=',
					root: '@',
					meta: '=',
					getParentMetadata: '&'
				},
				controller: ['$scope', '$rootScope', function($scope, $rootScope) {
					$scope.node = $scope.node || { op: '&&', items: [ ] };
					$scope.editingNode = angular.extend({ }, $scope.node);

					angular.extend($scope, {
						i18n: $rootScope.i18n,
						context: { },

						nodeUpdated: function(property, value) {
							var forceMetadata = false;
							if(property && $scope.context.hasOwnProperty(property)) {
								$scope.editingNode[property] = value;

								if(property === 'path') {
									delete $scope.context.op.options;
									$scope.editingNode.op = '';
									forceMetadata = true;
								}
							}

							$scope.getMetadata(function(metadata) {
								var ctx = $scope.context = {
									metadata: $scope.context.metadata,
									parentMeta: $scope.context.parentMeta,
									add: $scope.root,
									delete: !$scope.root,
									hover: false,
									validation: {
										path: [],
										op: [],
										value: [],
										valid: true
									},
									path: {
										editable: false,
										visible: true
									},
									op: {
										editable: false,
										visible: false
									},
									value: {
										editable: false,
										visible: false,
										editorType: null
									}
								};

								$filteringService.afterNodeEdit($scope.editingNode, ctx.metadata);

								var i;
								if(!ctx.op.options || !ctx.op.options.length) {
									ctx.op.options = $filter('applicableOps')($filteringService.allOps(), metadata);
									if(!$scope.editingNode.op && ctx.op.options.length) {
										$scope.editingNode.op = ctx.op.options[0];
									}
									for (i = 0; i < ctx.op.options.length; i++) {
										ctx.op.options[i] = {
											value: ctx.op.options[i],
											label: $filteringService.opDisplayName(ctx.op.options[i])
										};
									}
								}

								if(!ctx.path.options || !ctx.path.options.length) {
									ctx.path.options = $filteringService.applicablePaths(ctx.parentMeta);

									for(i = 0; i < ctx.path.options.length; i++) {
										ctx.path.options[i] = {
											value: ctx.path.options[i],
											label: $filteringService.pathDisplayName(ctx.path.options[i], ctx.parentMeta)
										};
									}

									ctx.path.options.sort(function(a, b) {
										if(a.value === '&&' || a.value === '||' || a.value === '&&!') {
											return -1;
										}
										if(b.value === '&&' || b.value === '||' || b.value === '&&!') {
											return 1;
										}
										if (a.label > b.label) {
											return 1;
										}
										if (a.label < b.label) {
											return -1;
										}
										return 0;
									});
								}

								var composite = $filteringService.isCompositeNode($scope.editingNode, metadata);
								var special = $filteringService.isSpecialNode($scope.editingNode);

								if(composite || special) {
									ctx.add = true;
									ctx.path.editable = !$scope.editingNode.items || !$scope.editingNode.items.length;
								}
								else {
									ctx.path.editable = true;
									ctx.op.editable = ctx.op.visible = !!$scope.editingNode.path;

									ctx.value.editable = ctx.value.visible = !!$scope.editingNode.path &&
										!!$scope.editingNode.op && !$filteringService.isUnaryNode($scope.editingNode);
									if(ctx.value.editable) {
										ctx.value.editorType = 'text';

										if (metadata.PropertyType === 'date' || metadata.PropertyType === 'datetime' ||
											metadata.PropertyType === 'boolean' || metadata.PropertyType === 'enum') {
											ctx.value.editorType = metadata.PropertyType;
										}
									}
								}

								if ($filteringService.validateNode($scope.editingNode, ctx.metadata, ctx.validation)) {
									$scope.node = angular.extend({ }, $scope.editingNode);
								}
								$filteringService.beforeNodeEdit($scope.editingNode);

								ctx.path.displayValue = $filteringService.pathDisplayName(
									$scope.editingNode.path, ctx.parentMeta, ctx.metadata);
								ctx.op.displayValue = $filteringService.opDisplayName(
									$scope.editingNode.op, $scope.editingNode.not, $scope.node.path);
								ctx.value.displayValue = $filteringService.valueDisplayName(
									$scope.editingNode.value, metadata);

							}, forceMetadata);

							if(property) {
								$scope.$parent.nodeUpdated();
							}
						},

						getMetadata: function(callback, forceRefresh) {
							if(forceRefresh || !$scope.context.metadata) {
								$scope.getParentMetadata({
									callback: function(parentMeta) {
										$filteringService.getNodeMetadata($scope.meta, $scope.editingNode, parentMeta, function(metadata) {
											$scope.context.metadata = metadata;
											$scope.context.parentMeta = parentMeta;
											callback($scope.context.metadata);
										});
									}
								});
								return;
							}
							callback($scope.context.metadata);
						},

						addNode: function() {
							if(!$scope.context.add) {
								return;
							}

							$scope.node.items.push({ op: '&&', items: [ ] });
							$scope.appendSubNode($scope.node.items.length - 1);
							$scope.nodeUpdated();
						},

						deleteNode: function() {
							if(!$scope.context.delete) {
								return;
							}

							var parentNode = $scope.$parent.node;
							if(!parentNode || !parentNode.items) {
								return;
							}

							var index = parentNode.items.indexOf($scope.node);
							if(index === -1) {
								return;
							}

							parentNode.items.splice(index, 1);
							$scope.$parent.renderSubNodes();
							$scope.$parent.nodeUpdated();
						}
					});

					if($scope.root) {
						$scope.$on('addRootNode', function() { $scope.addNode(); });

						$scope.$on('clear', function() {
							$scope.node.items = [];
							$scope.renderSubNodes();
						});
					}

					$scope.nodeUpdated();
				}],
				link: function($scope, element) {
					var childrenContainer = $('
');

					$scope.appendSubNode = function(index) {
						$compile([
							'
',
							'
'].join('\n'))
						($scope, function(cloned) {
							childrenContainer.append(cloned);
						});
					};

					$scope.renderSubNodes = function() {
						childrenContainer.html('');

						$scope.node = $scope.node || { };
						$scope.node.items = $scope.node.items || [];
						for(var i = 0; i < $scope.node.items.length; i++) {
							$scope.appendSubNode(i);
						}
					};

					compiledNodeTpl($scope, function(cloned) {
						element.replaceWith(cloned);
						childrenContainer = $('.nodeChildrenContainer', cloned);

						$scope.renderSubNodes();
					});
				}
			};
		}])
	.directive('filterValueEditor', [function() {
		return {
			link: function($scope, element, attrs) {
				attrs.$observe('filterValueEditor', function(editorType) {
					var linkFn = editorTpls[editorType];
					if(!linkFn) {
						return;
					}
					linkFn($scope, function(cloned) {
						element.html(cloned);
					});
				});
			}
		};
	}]);
});<commit_msg>Bug #1623<commit_after>define([
	'../../moduleDef',
	'jquery',
	'angular',
	'text!./structuredFilter.tpl.html',
	'text!./structuredFilterNode.tpl.html',
	'text!./booleanValueEditor.tpl.html',
	'text!./datetimeValueEditor.tpl.html',
	'text!./dateValueEditor.tpl.html',
	'text!./enumValueEditor.tpl.html',
	'text!./textValueEditor.tpl.html'
], function (module, $, angular, tpl, nodeTpl, booleanEditorTpl, datetimeEditorTpl,	dateEditorTpl, enumEditorTpl, textEditorTpl) {
	var editorTpls = { };

	module.directive('structuredFilter', ['$compile', 'helpers', 'filteringService', 'metadataService',
		function($compile, $helpers, $filteringService, $metadataService) {
			angular.extend(editorTpls, {
				boolean: $compile(booleanEditorTpl),
				datetime: $compile(datetimeEditorTpl),
				date: $compile(dateEditorTpl),
				enum: $compile(enumEditorTpl),
				text: $compile(textEditorTpl)
			});

			return {
				replace: true,
				template: tpl,
				scope: {
					rootNode: '=filterNgModel',
					meta: '='
				},
				controller: ['$scope', '$rootScope', function($scope, $rootScope) {
					angular.extend($scope, {
						i18n: $rootScope.i18n,

						getMetadata: function(callback, forceRefresh) {
							if(forceRefresh || !$scope.metadata) {
								$metadataService.modelMetadata($scope.meta, null, function(metadata) {
									$scope.metadata = metadata;
									callback($scope.metadata);
								});
								return;
							}
							callback($scope.metadata);
						},

						addNode: function() {
							$scope.$broadcast('addRootNode');
						},

						clear: function() {
							$scope.$broadcast('clear');
						}
					});

					$scope.$watch('rootNode', function(value) {
						if(value) {
							value.op = '&&';
							delete value.path;
							delete value.value;
							delete value.not;
							if(!value.items) {
								value.items = [];
							}
						}
						else {
							$scope.rootNode = { op: '&&', items: [ ] };
							$scope.clear();
						}
					}, true);
				}]
			};
		}
	])
	.directive('structuredFilterNode', ['helpers', 'filteringService', 'metadataService', '$compile', '$filter',
		function($helpers, $filteringService, $metadataService, $compile, $filter) {
			var compiledNodeTpl = $compile(nodeTpl);

			return {
				replace: true,
				scope: {
					node: '=',
					root: '@',
					meta: '=',
					getParentMetadata: '&'
				},
				controller: ['$scope', '$rootScope', function($scope, $rootScope) {
					$scope.node = $scope.node || { op: '&&', items: [ ] };
					$scope.editingNode = angular.extend({ }, $scope.node);

					angular.extend($scope, {
						i18n: $rootScope.i18n,
						context: { },

						nodeUpdated: function(property, value) {
							var forceMetadata = false;
							if(property && $scope.context.hasOwnProperty(property)) {
								$scope.editingNode[property] = value;

								if(property === 'path') {
									delete $scope.context.op.options;
									$scope.editingNode.op = '';
									forceMetadata = true;
								}
							}

							var ctx = $scope.context = {
								add: $scope.root,
								delete: !$scope.root,
								hover: false,
								validation: {
									path: [],
									op: [],
									value: [],
									valid: true
								},
								path: {
									editable: false,
									visible: true
								},
								op: {
									editable: false,
									visible: false
								},
								value: {
									editable: false,
									visible: false,
									editorType: null
								}
							};

							if($scope.root) {
								return;
							}

							$scope.getMetadata(function() {
								$filteringService.afterNodeEdit($scope.editingNode, ctx.metadata);

								var i;
								if(!ctx.op.options || !ctx.op.options.length) {
									ctx.op.options = $filter('applicableOps')($filteringService.allOps(), ctx.metadata);
									if(!$scope.editingNode.op && ctx.op.options.length) {
										$scope.editingNode.op = ctx.op.options[0];
									}
									for (i = 0; i < ctx.op.options.length; i++) {
										ctx.op.options[i] = {
											value: ctx.op.options[i],
											label: $filteringService.opDisplayName(ctx.op.options[i])
										};
									}
								}

								if(!ctx.path.options || !ctx.path.options.length) {
									ctx.path.options = $filteringService.applicablePaths(ctx.parentMeta);

									for(i = 0; i < ctx.path.options.length; i++) {
										ctx.path.options[i] = {
											value: ctx.path.options[i],
											label: $filteringService.pathDisplayName(ctx.path.options[i], ctx.parentMeta)
										};
									}

									ctx.path.options.sort(function(a, b) {
										if(a.value === '&&' || a.value === '||' || a.value === '&&!') {
											return -1;
										}
										if(b.value === '&&' || b.value === '||' || b.value === '&&!') {
											return 1;
										}
										if (a.label > b.label) {
											return 1;
										}
										if (a.label < b.label) {
											return -1;
										}
										return 0;
									});
								}

								var composite = $filteringService.isCompositeNode($scope.editingNode, ctx.metadata);
								var special = $filteringService.isSpecialNode($scope.editingNode);

								if(composite || special) {
									ctx.add = true;
									ctx.path.editable = !$scope.editingNode.items || !$scope.editingNode.items.length;
								}
								else {
									ctx.path.editable = true;
									ctx.op.editable = ctx.op.visible = !!$scope.editingNode.path;

									ctx.value.editable = ctx.value.visible = !!$scope.editingNode.path &&
										!!$scope.editingNode.op && !$filteringService.isUnaryNode($scope.editingNode);
									if(ctx.value.editable) {
										ctx.value.editorType = 'text';

										if (ctx.metadata.PropertyType === 'date' || ctx.metadata.PropertyType === 'datetime' ||
												ctx.metadata.PropertyType === 'boolean' || ctx.metadata.PropertyType === 'enum') {
											ctx.value.editorType = ctx.metadata.PropertyType;
										}
									}
								}

								if ($filteringService.validateNode($scope.editingNode, ctx.metadata, ctx.validation)) {
									$scope.node = angular.extend({ }, $scope.editingNode);
								}
								$filteringService.beforeNodeEdit($scope.editingNode);

								ctx.path.displayValue = $filteringService.pathDisplayName(
									$scope.editingNode.path, ctx.parentMeta, ctx.metadata);
								ctx.op.displayValue = $filteringService.opDisplayName(
									$scope.editingNode.op, $scope.editingNode.not, $scope.node.path);
								ctx.value.displayValue = $filteringService.valueDisplayName(
									$scope.editingNode.value, ctx.metadata);

							}, forceMetadata);

							if(property) {
								$scope.$parent.nodeUpdated();
							}
						},

						getMetadata: function(callback, forceRefresh) {
							if(forceRefresh || !$scope.context.metadata) {
								$scope.getParentMetadata({
									callback: function(parentMeta) {
										$filteringService.getNodeMetadata($scope.meta, $scope.editingNode, parentMeta, function(metadata) {
											$scope.context.metadata = metadata;
											$scope.context.parentMeta = parentMeta;
											callback($scope.context.metadata);
										});
									}
								});
								return;
							}
							callback($scope.context.metadata);
						},

						addNode: function() {
							if(!$scope.context.add) {
								return;
							}

							$scope.node.items.push({ op: '&&', items: [ ] });
							$scope.appendSubNode($scope.node.items.length - 1);
							$scope.nodeUpdated();
						},

						deleteNode: function() {
							if(!$scope.context.delete) {
								return;
							}

							var parentNode = $scope.$parent.node;
							if(!parentNode || !parentNode.items) {
								return;
							}

							var index = parentNode.items.indexOf($scope.node);
							if(index === -1) {
								return;
							}

							parentNode.items.splice(index, 1);
							$scope.$parent.renderSubNodes();
							$scope.$parent.nodeUpdated();
						}
					});

					if($scope.root) {
						$scope.$on('addRootNode', function() { $scope.addNode(); });

						$scope.$on('clear', function() {
							$scope.node.items = [];
							$scope.renderSubNodes();
						});
					}

					$scope.nodeUpdated();
				}],
				link: function($scope, element) {
					var childrenContainer = $('
');

					$scope.appendSubNode = function(index) {
						$compile([
							'
',
							'
'].join('\n'))
						($scope, function(cloned) {
							childrenContainer.append(cloned);
						});
					};

					$scope.renderSubNodes = function() {
						childrenContainer.html('');

						$scope.node = $scope.node || { };
						$scope.node.items = $scope.node.items || [];
						for(var i = 0; i < $scope.node.items.length; i++) {
							$scope.appendSubNode(i);
						}
					};

					compiledNodeTpl($scope, function(cloned) {
						element.replaceWith(cloned);
						childrenContainer = $('.nodeChildrenContainer', cloned);

						$scope.renderSubNodes();
					});
				}
			};
		}])
	.directive('filterValueEditor', [function() {
		return {
			link: function($scope, element, attrs) {
				attrs.$observe('filterValueEditor', function(editorType) {
					var linkFn = editorTpls[editorType];
					if(!linkFn) {
						return;
					}
					linkFn($scope, function(cloned) {
						element.html(cloned);
					});
				});
			}
		};
	}]);
});<|endoftext|>


<commit_before>// flow-typed signature: 3193712ae4ddd5385f359c7b985ac6fd
// flow-typed version: 1517003b23/react-hot-loader_v4.x.x/flow_>=v0.53.0

// @flow
declare module "react-hot-loader/root" {
    declare type Module = {
        id: string,
    };

    declare type AppContainerProps = {|
        children: React$Element<any>,
        errorBoundary?: boolean,
        errorReporter?: React$ComponentType<{
            error: Error,
            errorInfo: { componentStack: string },
        }>,
    |};

    declare export class AppContainer extends React$Component<AppContainerProps> {}

    declare export function hot(
        someModule: Module
    ): <T, W: React$ComponentType<T>>(
        wrappedComponent: W,
        props?: $Diff<AppContainerProps, { children: React$Element<any> }>
    ) => React$ComponentType<T>;
}
<commit_msg>Add types for react-hot-loader package<commit_after>// flow-typed signature: 3193712ae4ddd5385f359c7b985ac6fd
// flow-typed version: 1517003b23/react-hot-loader_v4.x.x/flow_>=v0.53.0

declare module "react-hot-loader/root" {
    declare type AppContainerProps = {
        errorBoundary?: boolean,
        errorReporter?: React$ComponentType<{
            error: Error,
            errorInfo: { componentStack: string },
        }>,
    };

    declare export function hot(
        Component: React$ComponentType<any>,
        props?: AppContainerProps
    ): React$ComponentType<any>;
}
<|endoftext|>


<commit_before>'use strict';

const SyncStrategy = require('./SyncStrategy');

const defaults = {
    syncsBufferLength: 5,
    maxReEnactSteps: 60,   // maximum number of steps to re-enact
    RTTEstimate: 2,        // estimate the RTT as two steps (for updateRate=6, that's 200ms)
    extrapolate: 2,        // player performs method "X" which means extrapolate to match server time. that 100 + (0..100)
    localObjBending: 0.1,  // amount of bending towards position of sync object
    remoteObjBending: 0.6, // amount of bending towards position of sync object
    bendingIncrements: 10   // the bending should be applied increments (how many steps for entire bend)
};

class ExtrapolateStrategy extends SyncStrategy {

    constructor(clientEngine, inputOptions) {

        const options = Object.assign({}, defaults, inputOptions);
        super(clientEngine, options);

        this.lastSync = null;
        this.needFirstSync = true;
        this.recentInputs = {};
        this.gameEngine = this.clientEngine.gameEngine;
        this.gameEngine.on('client__postStep', this.extrapolate.bind(this));
        this.gameEngine.on('client__syncReceived', this.collectSync.bind(this));
        this.gameEngine.on('client__processInput', this.clientInputSave.bind(this));
    }

    // keep a buffer of inputs so that we can replay them on extrapolation
    clientInputSave(inputData) {

        // if no inputs have been stored for this step, create an array
        if (!this.recentInputs[inputData.step]) {
            this.recentInputs[inputData.step] = [];
        }
        this.recentInputs[inputData.step].push(inputData);
    }

    // collect a sync and its events
    collectSync(e) {

        // on first connect we need to wait for a full world update
        if (this.needFirstSync) {
            if (!e.fullUpdate)
                return;
        } else {
            // ignore syncs which are older than the latest
            if (this.lastSync && this.lastSync.stepCount && this.lastSync.stepCount > e.stepCount)
                return;
        }

        // build new sync object
        let lastSync = this.lastSync = {};
        lastSync.stepCount = e.stepCount;

        // keep a reference of events by object id
        lastSync.syncObjects = {};
        e.syncEvents.forEach(sEvent => {
            let o = sEvent.objectInstance;
            if (!o) return;
            if (!lastSync.syncObjects[o.id]) {
                lastSync.syncObjects[o.id] = [];
            }
            lastSync.syncObjects[o.id].push(sEvent);
        });

        // keep a reference of events by step
        lastSync.syncSteps = {};
        e.syncEvents.forEach(sEvent => {

            // add an entry for this step and event-name
            if (!lastSync.syncSteps[sEvent.stepCount]) lastSync.syncSteps[sEvent.stepCount] = {};
            if (!lastSync.syncSteps[sEvent.stepCount][sEvent.eventName]) lastSync.syncSteps[sEvent.stepCount][sEvent.eventName] = [];
            lastSync.syncSteps[sEvent.stepCount][sEvent.eventName].push(sEvent);
        });

        let objCount = (Object.keys(lastSync.syncObjects)).length;
        let eventCount = e.syncEvents.length;
        let stepCount = (Object.keys(lastSync.syncSteps)).length;
        this.gameEngine.trace.debug(`sync contains ${objCount} objects ${eventCount} events ${stepCount} steps`);
    }

    // add an object to our world
    addNewObject(objId, newObj, options) {

        let curObj = new newObj.constructor();
        curObj.syncTo(newObj);
        this.gameEngine.addObjectToWorld(curObj);
        console.log(`adding new object ${curObj}`);

        return curObj;
    }

    // clean up the input buffer
    cleanRecentInputs() {
        let firstReplayStep = this.gameEngine.world.stepCount - this.options.extrapolate;
        for (let input of Object.keys(this.recentInputs)) {
            if (this.recentInputs[input].step < firstReplayStep) {
                delete this.recentInputs[input];
            }
        }
    }

    // apply a new sync
    applySync() {

        this.gameEngine.trace.debug('extrapolate applying sync');

        //
        //    scan all the objects in the sync
        //
        // 1. if the object has a local shadow, adopt the server object,
        //    and destroy the shadow
        //
        // 2. if the object exists locally, sync to the server object,
        //    later we will re-enact the missing steps and then bend to
        //    the current position
        //
        // 3. if the object is new, just create it
        //
        this.needFirstSync = false;
        let world = this.gameEngine.world;
        let serverStep = this.lastSync.stepCount;
        for (let ids of Object.keys(this.lastSync.syncObjects)) {

            // TODO: we are currently taking only the first event out of
            // the events that may have arrived for this object
            let ev = this.lastSync.syncObjects[ids][0];
            let curObj = world.objects[ev.objectInstance.id];

            let localShadowObj = this.gameEngine.findLocalShadow(ev.objectInstance);
            if (localShadowObj) {

                // case 1: this object has a local shadow object on the client
                this.gameEngine.trace.debug(`object ${ev.objectInstance.id} replacing local shadow ${localShadowObj.id}`);

                if (!world.objects.hasOwnProperty(ev.objectInstance.id)) {
                    let newObj = this.addNewObject(ev.objectInstance.id, ev.objectInstance, { visible: false });
                    newObj.saveState(localShadowObj);
                }
                this.gameEngine.removeObjectFromWorld(localShadowObj.id);

            } else if (curObj) {

                // case 2: this object already exists locally
                this.gameEngine.trace.trace(`object before syncTo: ${curObj.toString()}`);
                curObj.saveState();
                curObj.syncTo(ev.objectInstance);
                this.gameEngine.trace.trace(`object after syncTo: ${curObj.toString()} synced to step[${ev.stepCount}]`);

            } else {

                // case 3: object does not exist.  create it now
                this.addNewObject(ev.objectInstance.id, ev.objectInstance);
            }
        }

        //
        // reenact the steps that we want to extrapolate forwards
        //
        this.cleanRecentInputs();
        this.gameEngine.trace.debug(`extrapolate re-enacting steps from [${serverStep}] to [${world.stepCount}]`);
        if (serverStep < world.stepCount - this.options.maxReEnactSteps) {
            serverStep = world.stepCount - this.options.maxReEnactSteps;
            this.gameEngine.trace.info(`too many steps to re-enact.  Starting from [${serverStep}] to [${world.stepCount}]`);
        }

        let clientStep = world.stepCount;
        for (world.stepCount = serverStep; world.stepCount < clientStep;) {
            if (this.recentInputs[world.stepCount]) {
                this.recentInputs[world.stepCount].forEach(inputData => {

                    // only movement inputs are re-enacted
                    if (!inputData.inputOptions || !inputData.inputOptions.movement) return;

                    this.gameEngine.trace.trace(`extrapolate re-enacting movement input[${inputData.messageIndex}]: ${inputData.input}`);
                    this.gameEngine.processInput(inputData, this.clientEngine.playerId);
                });
            }

            // run the game engine step in "reenact" mode
            this.gameEngine.step(true);
        }

        //
        // bend back to original state
        //
        for (let objId of Object.keys(world.objects)) {

            // shadow objects are not bent
            if (objId >= this.gameEngine.options.clientIDSpace)
                continue;

            // TODO: using == instead of === because of string/number mismatch
            //       These values should always be strings (which contain a number)
            //       Reminder: the reason we use a string is that these
            //       values are sometimes used as object keys
            let obj = world.objects[objId];
            let isLocal = (obj.playerId == this.clientEngine.playerId); // eslint-disable-line eqeqeq
            let bending = isLocal ? this.options.localObjBending : this.options.remoteObjBending;
            obj.bendToCurrentState(bending, this.gameEngine.worldSettings, isLocal, this.options.bendingIncrements);
            if (typeof obj.refreshRenderObject === 'function')
                obj.refreshRenderObject();
            this.gameEngine.trace.trace(`object[${objId}] ${obj.bendingToString()}`);
        }

        // trace object state after sync
        for (let objId of Object.keys(world.objects)) {
            this.gameEngine.trace.trace(`object after extrapolate replay: ${world.objects[objId].toString()}`);
        }

        // destroy objects
        // TODO: use world.forEachObject((id, ob) => {});
        // TODO: identical code is in InterpolateStrategy
        for (let objId of Object.keys(world.objects)) {
            let objEvents = this.lastSync.syncObjects[objId];
            if (!objEvents || objId >= this.gameEngine.options.clientIDSpace)
                continue;

            objEvents.forEach((e) => {
                if (e.eventName === 'objectDestroy') this.gameEngine.removeObjectFromWorld(objId);
            });
        }
    }

    // Perform client-side extrapolation.
    extrapolate() {

        // apply incremental bending
        this.gameEngine.world.forEachObject((id, o) => {
            if (typeof o.applyIncrementalBending === 'function') {
                o.applyIncrementalBending();
                o.refreshToPhysics();
            }
        });

        // if there is a sync from the server, apply it now
        if (this.lastSync)
            this.applySync();
        this.lastSync = null;
    }
}

module.exports = ExtrapolateStrategy;
<commit_msg>exatrapolate apply syncs from past or present only<commit_after>'use strict';

const SyncStrategy = require('./SyncStrategy');

const defaults = {
    syncsBufferLength: 5,
    maxReEnactSteps: 60,   // maximum number of steps to re-enact
    RTTEstimate: 2,        // estimate the RTT as two steps (for updateRate=6, that's 200ms)
    extrapolate: 2,        // player performs method "X" which means extrapolate to match server time. that 100 + (0..100)
    localObjBending: 0.1,  // amount of bending towards position of sync object
    remoteObjBending: 0.6, // amount of bending towards position of sync object
    bendingIncrements: 10   // the bending should be applied increments (how many steps for entire bend)
};

class ExtrapolateStrategy extends SyncStrategy {

    constructor(clientEngine, inputOptions) {

        const options = Object.assign({}, defaults, inputOptions);
        super(clientEngine, options);

        this.lastSync = null;
        this.needFirstSync = true;
        this.recentInputs = {};
        this.gameEngine = this.clientEngine.gameEngine;
        this.gameEngine.on('client__postStep', this.extrapolate.bind(this));
        this.gameEngine.on('client__syncReceived', this.collectSync.bind(this));
        this.gameEngine.on('client__processInput', this.clientInputSave.bind(this));
    }

    // keep a buffer of inputs so that we can replay them on extrapolation
    clientInputSave(inputData) {

        // if no inputs have been stored for this step, create an array
        if (!this.recentInputs[inputData.step]) {
            this.recentInputs[inputData.step] = [];
        }
        this.recentInputs[inputData.step].push(inputData);
    }

    // collect a sync and its events
    collectSync(e) {

        // on first connect we need to wait for a full world update
        if (this.needFirstSync) {
            if (!e.fullUpdate)
                return;
        } else {
            // ignore syncs which are older than the latest
            if (this.lastSync && this.lastSync.stepCount && this.lastSync.stepCount > e.stepCount)
                return;
        }

        // build new sync object
        let lastSync = this.lastSync = {};
        lastSync.stepCount = e.stepCount;

        // keep a reference of events by object id
        lastSync.syncObjects = {};
        e.syncEvents.forEach(sEvent => {
            let o = sEvent.objectInstance;
            if (!o) return;
            if (!lastSync.syncObjects[o.id]) {
                lastSync.syncObjects[o.id] = [];
            }
            lastSync.syncObjects[o.id].push(sEvent);
        });

        // keep a reference of events by step
        lastSync.syncSteps = {};
        e.syncEvents.forEach(sEvent => {

            // add an entry for this step and event-name
            if (!lastSync.syncSteps[sEvent.stepCount]) lastSync.syncSteps[sEvent.stepCount] = {};
            if (!lastSync.syncSteps[sEvent.stepCount][sEvent.eventName]) lastSync.syncSteps[sEvent.stepCount][sEvent.eventName] = [];
            lastSync.syncSteps[sEvent.stepCount][sEvent.eventName].push(sEvent);
        });

        let objCount = (Object.keys(lastSync.syncObjects)).length;
        let eventCount = e.syncEvents.length;
        let stepCount = (Object.keys(lastSync.syncSteps)).length;
        this.gameEngine.trace.debug(`sync contains ${objCount} objects ${eventCount} events ${stepCount} steps`);
    }

    // add an object to our world
    addNewObject(objId, newObj, options) {

        let curObj = new newObj.constructor();
        curObj.syncTo(newObj);
        this.gameEngine.addObjectToWorld(curObj);
        console.log(`adding new object ${curObj}`);

        return curObj;
    }

    // clean up the input buffer
    cleanRecentInputs() {
        let firstReplayStep = this.gameEngine.world.stepCount - this.options.extrapolate;
        for (let input of Object.keys(this.recentInputs)) {
            if (this.recentInputs[input].step < firstReplayStep) {
                delete this.recentInputs[input];
            }
        }
    }

    // apply a new sync
    applySync() {

        this.gameEngine.trace.debug('extrapolate applying sync');

        //
        //    scan all the objects in the sync
        //
        // 1. if the object has a local shadow, adopt the server object,
        //    and destroy the shadow
        //
        // 2. if the object exists locally, sync to the server object,
        //    later we will re-enact the missing steps and then bend to
        //    the current position
        //
        // 3. if the object is new, just create it
        //
        this.needFirstSync = false;
        let world = this.gameEngine.world;
        let serverStep = this.lastSync.stepCount;
        for (let ids of Object.keys(this.lastSync.syncObjects)) {

            // TODO: we are currently taking only the first event out of
            // the events that may have arrived for this object
            let ev = this.lastSync.syncObjects[ids][0];
            let curObj = world.objects[ev.objectInstance.id];

            let localShadowObj = this.gameEngine.findLocalShadow(ev.objectInstance);
            if (localShadowObj) {

                // case 1: this object has a local shadow object on the client
                this.gameEngine.trace.debug(`object ${ev.objectInstance.id} replacing local shadow ${localShadowObj.id}`);

                if (!world.objects.hasOwnProperty(ev.objectInstance.id)) {
                    let newObj = this.addNewObject(ev.objectInstance.id, ev.objectInstance, { visible: false });
                    newObj.saveState(localShadowObj);
                }
                this.gameEngine.removeObjectFromWorld(localShadowObj.id);

            } else if (curObj) {

                // case 2: this object already exists locally
                this.gameEngine.trace.trace(`object before syncTo: ${curObj.toString()}`);
                curObj.saveState();
                curObj.syncTo(ev.objectInstance);
                this.gameEngine.trace.trace(`object after syncTo: ${curObj.toString()} synced to step[${ev.stepCount}]`);

            } else {

                // case 3: object does not exist.  create it now
                this.addNewObject(ev.objectInstance.id, ev.objectInstance);
            }
        }

        //
        // reenact the steps that we want to extrapolate forwards
        //
        this.cleanRecentInputs();
        this.gameEngine.trace.debug(`extrapolate re-enacting steps from [${serverStep}] to [${world.stepCount}]`);
        if (serverStep < world.stepCount - this.options.maxReEnactSteps) {
            serverStep = world.stepCount - this.options.maxReEnactSteps;
            this.gameEngine.trace.info(`too many steps to re-enact.  Starting from [${serverStep}] to [${world.stepCount}]`);
        }

        let clientStep = world.stepCount;
        for (world.stepCount = serverStep; world.stepCount < clientStep;) {
            if (this.recentInputs[world.stepCount]) {
                this.recentInputs[world.stepCount].forEach(inputData => {

                    // only movement inputs are re-enacted
                    if (!inputData.inputOptions || !inputData.inputOptions.movement) return;

                    this.gameEngine.trace.trace(`extrapolate re-enacting movement input[${inputData.messageIndex}]: ${inputData.input}`);
                    this.gameEngine.processInput(inputData, this.clientEngine.playerId);
                });
            }

            // run the game engine step in "reenact" mode
            this.gameEngine.step(true);
        }

        //
        // bend back to original state
        //
        for (let objId of Object.keys(world.objects)) {

            // shadow objects are not bent
            if (objId >= this.gameEngine.options.clientIDSpace)
                continue;

            // TODO: using == instead of === because of string/number mismatch
            //       These values should always be strings (which contain a number)
            //       Reminder: the reason we use a string is that these
            //       values are sometimes used as object keys
            let obj = world.objects[objId];
            let isLocal = (obj.playerId == this.clientEngine.playerId); // eslint-disable-line eqeqeq
            let bending = isLocal ? this.options.localObjBending : this.options.remoteObjBending;
            obj.bendToCurrentState(bending, this.gameEngine.worldSettings, isLocal, this.options.bendingIncrements);
            if (typeof obj.refreshRenderObject === 'function')
                obj.refreshRenderObject();
            this.gameEngine.trace.trace(`object[${objId}] ${obj.bendingToString()}`);
        }

        // trace object state after sync
        for (let objId of Object.keys(world.objects)) {
            this.gameEngine.trace.trace(`object after extrapolate replay: ${world.objects[objId].toString()}`);
        }

        // destroy objects
        // TODO: use world.forEachObject((id, ob) => {});
        // TODO: identical code is in InterpolateStrategy
        for (let objId of Object.keys(world.objects)) {
            let objEvents = this.lastSync.syncObjects[objId];
            if (!objEvents || objId >= this.gameEngine.options.clientIDSpace)
                continue;

            objEvents.forEach((e) => {
                if (e.eventName === 'objectDestroy') this.gameEngine.removeObjectFromWorld(objId);
            });
        }
    }

    // Perform client-side extrapolation.
    extrapolate() {

        // apply incremental bending
        this.gameEngine.world.forEachObject((id, o) => {
            if (typeof o.applyIncrementalBending === 'function') {
                o.applyIncrementalBending();
                o.refreshToPhysics();
            }
        });

        // if there is a sync from the server, from the past or present, apply it now
        if (this.lastSync && this.lastSync.stepCount <= this.gameEngine.world.stepCount) {
            this.applySync();
            this.lastSync = null;
        }
    }
}

module.exports = ExtrapolateStrategy;
<|endoftext|>


<commit_before>const React = require('react');
const deepForceUpdate = require('react-deep-force-update');
const Redbox = require('redbox-react');
const { Component } = React;

// Feature check for the createElement() patch.
// If createElement() was patched, types with
// the same __source will resolve to the same type.
let wasCreateElementPatched = false;
const A = () => {};
A.__source = { fileName: 'fake', localName: 'fake' }
const B = () => {};
B.__source = { fileName: 'fake', localName: 'fake' }
if (<A />.type === <B />.type) {
  wasCreateElementPatched = true;
}

class AppContainer extends Component {
  constructor(props) {
    super(props);
    this.state = { error: null };
  }

  componentDidMount() {
    if (!wasCreateElementPatched) {
      console.error(
        'React Hot Loader: It appears that "react-hot-loader/patch" ' +
        'did not run immediately before the app started. Make sure that it ' +
        'runs before any other code. For example, if you use Webpack, ' +
        'you can add "react-hot-loader/patch" as the very first item to the ' +
        '"entry" array in its config. Alternatively, you can add ' +
        'require("react-hot-loader/patch") as the very first line ' +
        'in the application code, before any other imports.'
      );
    }
  }

  componentWillReceiveProps(nextProps) {
    // Hot reload is happening.
    // Retry rendering!
    if (nextProps.children.type !== this.props.children.type) {
      this.setState({
        error: null
      });
    }
  }

  componentDidUpdate(prevProps) {
    // Hot reload has finished.
    // Force-update the whole tree, including
    // components that refuse to update.
    if (prevProps.children.type !== this.props.children.type) {
      deepForceUpdate(this);
    }
  }

  // This hook is going to become official in React 15.x.
  // In 15.0, it only catches errors on initial mount.
  // Later it will work for updates as well:
  // https://github.com/facebook/react/pull/6020
  unstable_handleError(error) {
    this.setState({
      error: error
    });
  }

  render() {
    const { error } = this.state;
    if (error) {
      return <this.props.errorReporter error={error} />;
    } else {
      return React.Children.only(this.props.children);
    }
  }
}

AppContainer.propTypes = {
  children: function (props, propName, componentName, location, propFullName) {
    if (typeof props[propName].type !== 'function') {
      return new Error(`Invalid prop ${propFullName} supplied to ${componentName}. Expected a single React element with your app’s root component, e.g. <App />.`);
    }

    if (React.Children.count(props[propName]) > 1) {
      return new Error(`Invalid prop ${propFullName} supplied to ${componentName}. Expected a single React element with your app’s root component, e.g. <App />.`);
    }
  }
};

AppContainer.defaultProps = {
  errorReporter: Redbox
};

module.exports = AppContainer;
<commit_msg>Add old API for backwards compatibility.<commit_after>const React = require('react');
const deepForceUpdate = require('react-deep-force-update');
const Redbox = require('redbox-react');
const { Component } = React;

// Feature check for the createElement() patch.
// If createElement() was patched, types with
// the same __source will resolve to the same type.
let wasCreateElementPatched = false;
const A = () => {};
A.__source = { fileName: 'fake', localName: 'fake' }
const B = () => {};
B.__source = { fileName: 'fake', localName: 'fake' }
if (<A />.type === <B />.type) {
  wasCreateElementPatched = true;
}

class AppContainer extends Component {
  constructor(props) {
    super(props);
    this.state = { error: null };
  }

  componentDidMount() {
    if (!wasCreateElementPatched) {
      console.error(
        'React Hot Loader: It appears that "react-hot-loader/patch" ' +
        'did not run immediately before the app started. Make sure that it ' +
        'runs before any other code. For example, if you use Webpack, ' +
        'you can add "react-hot-loader/patch" as the very first item to the ' +
        '"entry" array in its config. Alternatively, you can add ' +
        'require("react-hot-loader/patch") as the very first line ' +
        'in the application code, before any other imports.'
      );
    }
  }

  componentWillReceiveProps(nextProps) {
    // Hot reload is happening.
    // Retry rendering!
    if (nextProps.component !== this.props.component) {
      this.setState({
        error: null
      });
    }

    if (nextProps.children.type !== this.props.children.type) {
      this.setState({
        error: null
      });
    }
  }

  componentDidUpdate(prevProps) {
    // Hot reload has finished.
    // Force-update the whole tree, including
    // components that refuse to update.
    if (prevProps.component !== this.props.component) {
      deepForceUpdate(this);
    }

    if (prevProps.children.type !== this.props.children.type) {
      deepForceUpdate(this);
    }
  }

  // This hook is going to become official in React 15.x.
  // In 15.0, it only catches errors on initial mount.
  // Later it will work for updates as well:
  // https://github.com/facebook/react/pull/6020
  unstable_handleError(error) {
    this.setState({
      error: error
    });
  }

  render() {
    const { error } = this.state;
    if (error) {
      return <this.props.errorReporter error={error} />;
    }

    if (this.props.component) {
      return <this.props.component {...this.props.props} />;
    }
    else {
      return React.Children.only(this.props.children);
    }
  }
}

AppContainer.propTypes = {
  children: function (props, propName, componentName, location, propFullName) {
    if (typeof props[propName].type !== 'function') {
      return new Error(`Invalid prop ${propFullName} supplied to ${componentName}. Expected a single React element with your app’s root component, e.g. <App />.`);
    }

    if (React.Children.count(props[propName]) > 1) {
      return new Error(`Invalid prop ${propFullName} supplied to ${componentName}. Expected a single React element with your app’s root component, e.g. <App />.`);
    }
  }
};

AppContainer.defaultProps = {
  errorReporter: Redbox
};

module.exports = AppContainer;
<|endoftext|>