<commit_before>import _ from "lodash";
import { matchPath } from "react-router";
import fetch from "universal-fetch";
import {
  isBrowser,
  loadScript,
  loadStyle,
  generateStringHash
} from "./utils";

let globalsLoaded = false;

let globals = {
  routes: []
};

export const setGlobalRoutes = (routes) => {
  globals.routes = _.cloneDeep(routes);
};
/**
 * Load globals through the express route, we don't want to increase size of page
 * adding it to script tag
 * @returns {Promise.<*>}
 */
export const loadGlobals = async () => {
  if (!isBrowser()) {
    return Promise.reject();
  }

  if (globalsLoaded) {
    return Promise.resolve(globals);
  }

  return fetch(`${location.protocol}//${location.host}/_globals`)
    .then(res => res.json())
    .then(responseBody => {
      _.each(responseBody, (value, key) => {
        _.set(globals, key, value);
      });
      globalsLoaded = true;
      return globals;
    })
    .catch(ex => {
      // eslint-disable-next-line
      console.log("Cannot load _globals: ", ex);
    });
};

/**
 * Get requested module by specifying path
 * @param pathname
 * @param routes
 * @returns {boolean}
 */
export const getModuleByUrl = (pathname, routes = globals.routes) => {
  let moduleName = false;

  // Try to get module if exact path is matched

  /**
   * Iterate through all the routes to get
   * the correct module name for the path.
   * This iteration is for exact match with pathname
   * thus giving more priority to /about/about-us to more than
   * /:something/:sub-something
   */
  _.each(routes, route => {

    // If already found a module name then return
    if (moduleName) return;

    // if current route is abstract then try to
    // search for sub routes
    if (_.get(route, "abstract", false)) {
      if (_.get(route, "routes", []).length) {
        moduleName = getModuleByUrl(pathname, route.routes);
      }
    } else if(route.path === pathname) {
      moduleName = route.bundleKey;
    }
  });

  /**
   * If no module name is found via match of react-router
   * i.e. using matchPath
   */
  if (!moduleName) {
    // Iterate through all the routes to get
    // the correct module name for the path
    _.each(routes, route => {

      // If already found a module name then return
      if (moduleName) return;

      // if current route is abstract then try to
      // search for sub routes
      if (_.get(route, "abstract", false)) {
        if (_.get(route, "routes", []).length) {
          moduleName = getModuleByUrl(pathname, route.routes);
        }
      } else if(matchPath(pathname, route)) {
        moduleName = route.bundleKey;
      }
    });
  }

  return moduleName;
};

/**
 * Check if script requested belongs to the modules specified
 * check if public/build/mod-home-cat-1982Aasdd12ascgt3.bundle.js
 * belongs to home module or home-cat module
 *
 * @param script
 * @param mod
 * @returns {boolean}
 */
export const scriptBelongToMod = (script, mod) => {
  const finalFileName = script.split("/").pop();
  if (!finalFileName) {
    return false;
  }
  let fileNameWithoutHash = finalFileName.split(".");
  if (fileNameWithoutHash.length < 4) {
    return false;
  }
  // Remove extension
  fileNameWithoutHash.pop();

  // Remove "bundle"
  fileNameWithoutHash.pop();

  // remove "hash"
  fileNameWithoutHash.pop();

  // Join with "." again
  fileNameWithoutHash = fileNameWithoutHash.join(".");

  return fileNameWithoutHash === `mod-${mod}`;
};

/**
 * Keep track of modules loaded
 * @type {Array}
 */
let modulesLoaded = [];
/**
 * Load url specific modules & trigger call backs on load
 * @param url
 * @param cb
 */
export const loadModuleByUrl = (url, cb = () => {}) => {

  if (!isBrowser()) {
    cb();
    return;
  }

  loadGlobals().then(() => {

    const currentMod = getModuleByUrl(url, globals.routes);

    //eslint-disable-next-line
    debugger;

    // location is an object like window.location
    // Load in respect to path
    let isLoaded = false;
    const afterLoad = () => {
      isLoaded = true;
      modulesLoaded.push(currentMod);
      cb(currentMod);
      window.removeEventListener("routesload", afterLoad);
    };
    window.addEventListener("routesload", afterLoad);

    // Try to load after 5 second even if script does not call event
    const extendedAfterLoad = () => {
      setTimeout(() => {
        if (!isLoaded) {
          afterLoad();
        }
      }, 5000);
    };

    let listOfPromises = [];
    _.each(globals.allCss, css => {
      if (scriptBelongToMod(css, currentMod)) {
        listOfPromises.push(loadStyle(css));
      }
    });

    _.each(globals.allJs, js => {
      if (scriptBelongToMod(js,currentMod)) {
        listOfPromises.push(loadScript(js));
      }
    });
    Promise.all(listOfPromises).then(extendedAfterLoad).catch(extendedAfterLoad);
  });
};

/**
 * Check if module for the url is loaded or not
 * @param url
 * @returns {boolean}
 */
export const isModuleLoaded = (url, routes = globals.routes) => {
  let mod = getModuleByUrl(url, routes);
  return _.indexOf(modulesLoaded, mod) !== -1;
};

/**
 * List of hash of files pre-loaded
 * @type {Array}
 */
let preLoadedFiles = [];
/**
 * Get list of pending pre-loads files
 * @returns {[*,*]}
 */
const getPendingPreloadFiles = () => {
  let pendingCss = [];
  let pendingJs = [];

  _.each(globals.allCss, css => {
    let cssHash = generateStringHash(css, "PRELOAD");
    if (
      css.indexOf("mod-") !== -1 &&
      _.indexOf(preLoadedFiles, cssHash) === -1 &&
      _.indexOf(preLoadingFiles, cssHash) === -1) {
      pendingCss.push(css);
    }
  });

  _.each(globals.allJs, js => {
    let jsHash = generateStringHash(js, "PRELOAD");
    if (
      js.indexOf("mod-") !== -1 &&
      _.indexOf(preLoadedFiles, jsHash) === -1 &&
      _.indexOf(preLoadingFiles, jsHash) === -1) {
      pendingJs.push(js);
    }
  });
  return [
    pendingCss,
    pendingJs,
  ];
};

/**
 * Preload a script, compatible with ie 6,7,8+ and all major browsers
 * @param path
 * @param fn
 * @param scope
 */
const preLoadingFiles = [];
export const preLoadFile = (path, fn = () => {}, scope) => {

  if (!isBrowser()) {
    // If not a browser then do not allow loading of
    // css file, return with success->false
    fn.call( scope, false );
  }

  const pathHash = generateStringHash(path, "PRELOAD");

  if (_.indexOf(preLoadedFiles, pathHash) !== -1) {
    // Give a success callback
    fn.call( scope || window, true);
  }
  if (_.indexOf(preLoadingFiles, pathHash) !== -1) {
    return;
  }
  preLoadingFiles.push(pathHash);
  const isIE = navigator.appName.indexOf("Microsoft") === 0;
  let s, r;
  if (isIE) {
    // Load for internet explorer
    r = false;
    s = new Image();
    s.width = 0;
    s.height = 0;
    s.id = pathHash;
    s.style="display:none;";
    s.onload = s.onreadystatechange = function() {
      if(!r && (!this.readyState || this.readyState === "complete")) {
        r = true;
        preLoadedFiles.push(pathHash);
        fn.call( scope || window, true, s );
      }
    };
    s.onerror = function() {
      preLoadedFiles.push(pathHash);
      fn.call( scope || window, true, s );
    };
    s.src = path;
    return s;
  }
  s = document.createElement("object");
  s.width = 0;
  s.height = 0;
  s.style = "position:fixed; left:-200vw;top: -200vh; visibility:hidden;";
  s.id = pathHash;
  s.onload = s.onreadystatechange = function() {
    if (!r && (!this.readyState || this.readyState === "complete")) {
      r = true;
      preLoadedFiles.push(pathHash);
      fn.call( scope || window, true, s );
    }
  };
  s.onerror = function() {
    preLoadedFiles.push(pathHash);
    fn.call( scope || window, true, s );
  };
  s.data = path;
  document.body.appendChild(s);
  return s;
};

/**
 * Detect idle time for user and download assets accordingly
 * @param idleTime
 */
let timerEventInitialized  = false;
export const idlePreload = (idleTime = 10000) => {
  if (!isBrowser()) return;

  const concurrentLoading = 1;

  let timerInt;

  let loadingFile = false;
  let loadedFiles = 0;
  const preload = () => {
    if (loadingFile) {
      return;
    }

    const [pendingCss, pendingJs] = getPendingPreloadFiles();

    // Beauty is everything load css first
    // and then other files
    // BUT Load one file at a time
    let filesToBeLoaded = [];
    let isCss = false;

    if (pendingCss && pendingCss.length) {
      filesToBeLoaded = _.take(pendingCss, 1);
      isCss = true;
    } else if(pendingJs && pendingJs.length) {
      filesToBeLoaded = _.take(pendingJs, 1);
    }

    loadingFile = !!filesToBeLoaded.length;
    _.each(filesToBeLoaded, path => {
      preLoadFile(path, () => {
        loadedFiles++;

        if (isCss) {
          // Load stylesheet right away
          loadStyle(path);
        }
        // If files loaded change loadingFile to false
        if (loadedFiles >= concurrentLoading) {
          loadedFiles = 0;
          loadingFile = false;
          timerInt = setTimeout(preload, 100);
        }
      });
    });
  };

  const resetTimer = _.debounce(() => {
    clearTimeout(timerInt);
    timerInt = setTimeout(() => {
      preload();
    }, idleTime);  // time is in milliseconds
  }, 10);

  timerInt = setTimeout(() => {
    preload();
  }, idleTime);  // time is in milliseconds

  if (!timerEventInitialized) {
    window.onload = resetTimer;
    window.onmousemove = resetTimer;
    window.onmousedown = resetTimer; // catches touchscreen presses
    window.onclick = resetTimer;     // catches touchpad clicks
    window.onscroll = resetTimer;    // catches scrolling with arrow keys
    window.onkeypress = resetTimer;
    timerEventInitialized = true;
  }
};

/**
 * Get routes from path
 * @param routes
 * @param path
 */
export const getRouteFromPath = (path, routes = globals.routes) => {
  let selectedRoute = [];

  const changeMatchToNull =(route) => {
    if (route.match) {
      route.match = null;
    }
    if (route.routes && route.routes.length) {
      _.each(route.routes, subRoute => changeMatchToNull(subRoute));
    }
  };

  const bundleKey = getModuleByUrl(path, routes);

  _.each(routes, route => {
    if (route.bundleKey !== bundleKey) return;
    if (_.get(route, "abstract", false)) {
      // If abstract is present then Try to see if sub-routes matches
      // the path.

      if (route.routes && route.routes.length) {
        // If subRoutes are found to match the provided path,
        // that means we can add the abstract path to list of
        // our routes
        const subRoutes = getRouteFromPath(path, route.routes);

        if (subRoutes.length) {
          // Add abstract path to our list in expected order and then
          // add sub routes accordingly
          selectedRoute.push(_.assignIn(route, {match: null}));
          selectedRoute.push(...subRoutes);
        } else {
          changeMatchToNull(route);
        }
      }
    } else {

      // If route.path is found and route is not abstract
      // match with the path and if it matches try to match sub-routes
      // as well
      const match = matchPath(path, route);
      if(match) {
        selectedRoute.push(_.assignIn(route, {match: match}));
        if (route.routes && route.routes.length) {
          selectedRoute.push(...getRouteFromPath(path, route.routes));
        }
      } else {
        changeMatchToNull(route);
      }
    }
  });
  // Do not repeat paths even if the provided routes to the function has repeated paths
  // Thus make them unique by path
  return _.uniqBy(selectedRoute, "path");
};

/**
 * Get end/exact route for path
 * @param routes
 * @param path
 */
export const getExactRouteFromPath = (path, routes = globals.routes) => {
  const currentRoutes = getRouteFromPath(path, routes);
  return _.find(currentRoutes, r => _.get(r, "match.isExact", false));
};

const addBundleKeyToRoutes = (routes, bundleKey = "") => {
  if (!_.isArray(routes)) {
    routes.bundleKey = bundleKey;
    _.isArray(routes.routes) && addBundleKeyToRoutes(routes.routes);
  } else {
    _.each(routes, route => {
      route.bundleKey = bundleKey;
      _.isArray(route.routes) && addBundleKeyToRoutes(route.routes);
    });
  }
  return routes;
};

export const configureRoutes = (routes) => {
  let finalRoutes = [];
  _.each(routes, route => {
    const bundleKey = route.bundleKey || "";
    finalRoutes = [
      ...finalRoutes,
      ...addBundleKeyToRoutes(route.default, bundleKey)
    ];
  });
  return finalRoutes;
};<commit_msg>Solve Bug: Bundle key not added to sub-routes<commit_after>import _ from "lodash";
import { matchPath } from "react-router";
import fetch from "universal-fetch";
import {
  isBrowser,
  loadScript,
  loadStyle,
  generateStringHash
} from "./utils";

let globalsLoaded = false;

let globals = {
  routes: []
};

export const setGlobalRoutes = (routes) => {
  globals.routes = _.cloneDeep(routes);
};
/**
 * Load globals through the express route, we don't want to increase size of page
 * adding it to script tag
 * @returns {Promise.<*>}
 */
export const loadGlobals = async () => {
  if (!isBrowser()) {
    return Promise.reject();
  }

  if (globalsLoaded) {
    return Promise.resolve(globals);
  }

  return fetch(`${location.protocol}//${location.host}/_globals`)
    .then(res => res.json())
    .then(responseBody => {
      _.each(responseBody, (value, key) => {
        _.set(globals, key, value);
      });
      globalsLoaded = true;
      return globals;
    })
    .catch(ex => {
      // eslint-disable-next-line
      console.log("Cannot load _globals: ", ex);
    });
};

/**
 * Get requested module by specifying path
 * @param pathname
 * @param routes
 * @returns {boolean}
 */
export const getModuleByUrl = (pathname, routes = globals.routes) => {
  let moduleName = false;

  // Try to get module if exact path is matched

  /**
   * Iterate through all the routes to get
   * the correct module name for the path.
   * This iteration is for exact match with pathname
   * thus giving more priority to /about/about-us to more than
   * /:something/:sub-something
   */
  _.each(routes, route => {

    // If already found a module name then return
    if (moduleName) return;

    // if current route is abstract then try to
    // search for sub routes
    if (_.get(route, "abstract", false)) {
      if (_.get(route, "routes", []).length) {
        moduleName = getModuleByUrl(pathname, route.routes);
      }
    } else if(route.path === pathname) {
      moduleName = route.bundleKey;
    }
  });

  /**
   * If no module name is found via match of react-router
   * i.e. using matchPath
   */
  if (!moduleName) {
    // Iterate through all the routes to get
    // the correct module name for the path
    _.each(routes, route => {

      // If already found a module name then return
      if (moduleName) return;

      // if current route is abstract then try to
      // search for sub routes
      if (_.get(route, "abstract", false)) {
        if (_.get(route, "routes", []).length) {
          moduleName = getModuleByUrl(pathname, route.routes);
        }
      } else if(matchPath(pathname, route)) {
        moduleName = route.bundleKey;
      }
    });
  }

  return moduleName;
};

/**
 * Check if script requested belongs to the modules specified
 * check if public/build/mod-home-cat-1982Aasdd12ascgt3.bundle.js
 * belongs to home module or home-cat module
 *
 * @param script
 * @param mod
 * @returns {boolean}
 */
export const scriptBelongToMod = (script, mod) => {
  const finalFileName = script.split("/").pop();
  if (!finalFileName) {
    return false;
  }
  let fileNameWithoutHash = finalFileName.split(".");
  if (fileNameWithoutHash.length < 4) {
    return false;
  }
  // Remove extension
  fileNameWithoutHash.pop();

  // Remove "bundle"
  fileNameWithoutHash.pop();

  // remove "hash"
  fileNameWithoutHash.pop();

  // Join with "." again
  fileNameWithoutHash = fileNameWithoutHash.join(".");

  return fileNameWithoutHash === `mod-${mod}`;
};

/**
 * Keep track of modules loaded
 * @type {Array}
 */
let modulesLoaded = [];
/**
 * Load url specific modules & trigger call backs on load
 * @param url
 * @param cb
 */
export const loadModuleByUrl = (url, cb = () => {}) => {

  if (!isBrowser()) {
    cb();
    return;
  }

  loadGlobals().then(() => {

    const currentMod = getModuleByUrl(url, globals.routes);

    //eslint-disable-next-line
    debugger;

    // location is an object like window.location
    // Load in respect to path
    let isLoaded = false;
    const afterLoad = () => {
      isLoaded = true;
      modulesLoaded.push(currentMod);
      cb(currentMod);
      window.removeEventListener("routesload", afterLoad);
    };
    window.addEventListener("routesload", afterLoad);

    // Try to load after 5 second even if script does not call event
    const extendedAfterLoad = () => {
      setTimeout(() => {
        if (!isLoaded) {
          afterLoad();
        }
      }, 5000);
    };

    let listOfPromises = [];
    _.each(globals.allCss, css => {
      if (scriptBelongToMod(css, currentMod)) {
        listOfPromises.push(loadStyle(css));
      }
    });

    _.each(globals.allJs, js => {
      if (scriptBelongToMod(js,currentMod)) {
        listOfPromises.push(loadScript(js));
      }
    });
    Promise.all(listOfPromises).then(extendedAfterLoad).catch(extendedAfterLoad);
  });
};

/**
 * Check if module for the url is loaded or not
 * @param url
 * @returns {boolean}
 */
export const isModuleLoaded = (url, routes = globals.routes) => {
  let mod = getModuleByUrl(url, routes);
  return _.indexOf(modulesLoaded, mod) !== -1;
};

/**
 * List of hash of files pre-loaded
 * @type {Array}
 */
let preLoadedFiles = [];
/**
 * Get list of pending pre-loads files
 * @returns {[*,*]}
 */
const getPendingPreloadFiles = () => {
  let pendingCss = [];
  let pendingJs = [];

  _.each(globals.allCss, css => {
    let cssHash = generateStringHash(css, "PRELOAD");
    if (
      css.indexOf("mod-") !== -1 &&
      _.indexOf(preLoadedFiles, cssHash) === -1 &&
      _.indexOf(preLoadingFiles, cssHash) === -1) {
      pendingCss.push(css);
    }
  });

  _.each(globals.allJs, js => {
    let jsHash = generateStringHash(js, "PRELOAD");
    if (
      js.indexOf("mod-") !== -1 &&
      _.indexOf(preLoadedFiles, jsHash) === -1 &&
      _.indexOf(preLoadingFiles, jsHash) === -1) {
      pendingJs.push(js);
    }
  });
  return [
    pendingCss,
    pendingJs,
  ];
};

/**
 * Preload a script, compatible with ie 6,7,8+ and all major browsers
 * @param path
 * @param fn
 * @param scope
 */
const preLoadingFiles = [];
export const preLoadFile = (path, fn = () => {}, scope) => {

  if (!isBrowser()) {
    // If not a browser then do not allow loading of
    // css file, return with success->false
    fn.call( scope, false );
  }

  const pathHash = generateStringHash(path, "PRELOAD");

  if (_.indexOf(preLoadedFiles, pathHash) !== -1) {
    // Give a success callback
    fn.call( scope || window, true);
  }
  if (_.indexOf(preLoadingFiles, pathHash) !== -1) {
    return;
  }
  preLoadingFiles.push(pathHash);
  const isIE = navigator.appName.indexOf("Microsoft") === 0;
  let s, r;
  if (isIE) {
    // Load for internet explorer
    r = false;
    s = new Image();
    s.width = 0;
    s.height = 0;
    s.id = pathHash;
    s.style="display:none;";
    s.onload = s.onreadystatechange = function() {
      if(!r && (!this.readyState || this.readyState === "complete")) {
        r = true;
        preLoadedFiles.push(pathHash);
        fn.call( scope || window, true, s );
      }
    };
    s.onerror = function() {
      preLoadedFiles.push(pathHash);
      fn.call( scope || window, true, s );
    };
    s.src = path;
    return s;
  }
  s = document.createElement("object");
  s.width = 0;
  s.height = 0;
  s.style = "position:fixed; left:-200vw;top: -200vh; visibility:hidden;";
  s.id = pathHash;
  s.onload = s.onreadystatechange = function() {
    if (!r && (!this.readyState || this.readyState === "complete")) {
      r = true;
      preLoadedFiles.push(pathHash);
      fn.call( scope || window, true, s );
    }
  };
  s.onerror = function() {
    preLoadedFiles.push(pathHash);
    fn.call( scope || window, true, s );
  };
  s.data = path;
  document.body.appendChild(s);
  return s;
};

/**
 * Detect idle time for user and download assets accordingly
 * @param idleTime
 */
let timerEventInitialized  = false;
export const idlePreload = (idleTime = 10000) => {
  if (!isBrowser()) return;

  const concurrentLoading = 1;

  let timerInt;

  let loadingFile = false;
  let loadedFiles = 0;
  const preload = () => {
    if (loadingFile) {
      return;
    }

    const [pendingCss, pendingJs] = getPendingPreloadFiles();

    // Beauty is everything load css first
    // and then other files
    // BUT Load one file at a time
    let filesToBeLoaded = [];
    let isCss = false;

    if (pendingCss && pendingCss.length) {
      filesToBeLoaded = _.take(pendingCss, 1);
      isCss = true;
    } else if(pendingJs && pendingJs.length) {
      filesToBeLoaded = _.take(pendingJs, 1);
    }

    loadingFile = !!filesToBeLoaded.length;
    _.each(filesToBeLoaded, path => {
      preLoadFile(path, () => {
        loadedFiles++;

        if (isCss) {
          // Load stylesheet right away
          loadStyle(path);
        }
        // If files loaded change loadingFile to false
        if (loadedFiles >= concurrentLoading) {
          loadedFiles = 0;
          loadingFile = false;
          timerInt = setTimeout(preload, 100);
        }
      });
    });
  };

  const resetTimer = _.debounce(() => {
    clearTimeout(timerInt);
    timerInt = setTimeout(() => {
      preload();
    }, idleTime);  // time is in milliseconds
  }, 10);

  timerInt = setTimeout(() => {
    preload();
  }, idleTime);  // time is in milliseconds

  if (!timerEventInitialized) {
    window.onload = resetTimer;
    window.onmousemove = resetTimer;
    window.onmousedown = resetTimer; // catches touchscreen presses
    window.onclick = resetTimer;     // catches touchpad clicks
    window.onscroll = resetTimer;    // catches scrolling with arrow keys
    window.onkeypress = resetTimer;
    timerEventInitialized = true;
  }
};

/**
 * Get routes from path
 * @param routes
 * @param path
 */
export const getRouteFromPath = (path, routes = globals.routes) => {
  let selectedRoute = [];

  const changeMatchToNull =(route) => {
    if (route.match) {
      route.match = null;
    }
    if (route.routes && route.routes.length) {
      _.each(route.routes, subRoute => changeMatchToNull(subRoute));
    }
  };

  const bundleKey = getModuleByUrl(path, routes);

  _.each(routes, route => {
    if (route.bundleKey !== bundleKey) return;
    if (_.get(route, "abstract", false)) {
      // If abstract is present then Try to see if sub-routes matches
      // the path.

      if (route.routes && route.routes.length) {
        // If subRoutes are found to match the provided path,
        // that means we can add the abstract path to list of
        // our routes
        const subRoutes = getRouteFromPath(path, route.routes);

        if (subRoutes.length) {
          // Add abstract path to our list in expected order and then
          // add sub routes accordingly
          selectedRoute.push(_.assignIn(route, {match: null}));
          selectedRoute.push(...subRoutes);
        } else {
          changeMatchToNull(route);
        }
      }
    } else {

      // If route.path is found and route is not abstract
      // match with the path and if it matches try to match sub-routes
      // as well
      const match = matchPath(path, route);
      if(match) {
        selectedRoute.push(_.assignIn(route, {match: match}));
        if (route.routes && route.routes.length) {
          selectedRoute.push(...getRouteFromPath(path, route.routes));
        }
      } else {
        changeMatchToNull(route);
      }
    }
  });
  // Do not repeat paths even if the provided routes to the function has repeated paths
  // Thus make them unique by path
  return _.uniqBy(selectedRoute, "path");
};

/**
 * Get end/exact route for path
 * @param routes
 * @param path
 */
export const getExactRouteFromPath = (path, routes = globals.routes) => {
  const currentRoutes = getRouteFromPath(path, routes);
  return _.find(currentRoutes, r => _.get(r, "match.isExact", false));
};

const addBundleKeyToRoutes = (routes, bundleKey = "") => {
  if (!_.isArray(routes)) {
    routes.bundleKey = bundleKey;
    _.isArray(routes.routes) && addBundleKeyToRoutes(routes.routes, bundleKey);
  } else {
    _.each(routes, route => {
      route.bundleKey = bundleKey;
      _.isArray(route.routes) && addBundleKeyToRoutes(route.routes, bundleKey);
    });
  }
  return routes;
};

export const configureRoutes = (routes) => {
  let finalRoutes = [];
  _.each(routes, route => {
    const bundleKey = route.bundleKey || "";
    finalRoutes = [
      ...finalRoutes,
      ...addBundleKeyToRoutes(route.default, bundleKey)
    ];
  });
  return finalRoutes;
};<|endoftext|>


<commit_before>/* ======= Default Directives ======= */
directives[config.prefix + "if"] = function(el, val, vdom) {
  var evaluated = new Function("return " + val);
  if(!evaluated()) {
    el.textContent = "";
  } else {
    el.textContent = compileTemplate(vdom.val, self.$data);
  }
}

directives[config.prefix + "show"] = function(el, val, vdom) {
  var evaluated = new Function("return " + val);
  if(!evaluated()) {
    el.style.display = 'none';
  } else {
    el.style.display = 'block';
  }
}

directives[config.prefix + "on"] = function(el, val, vdom) {
  var splitVal = val.split(":");
  var eventToCall = splitVal[0];
  var methodToCall = splitVal[1];
  el.addEventListener(eventToCall, function() {
    self.method(methodToCall);
  });
  delete vdom.props[config.prefix + "on"];
}

directives[config.prefix + "model"] = function(el, val, vdom) {
  el.value = self.get(val);
  el.addEventListener("input", function() {
    self.set(val, el.value);
  });
  delete vdom.props[config.prefix + "model"];
}

directives[config.prefix + "for"] = function(el, val, vdom) {
  var parts = val.split(" in ");
  var alias = parts[0];
  var array = self.get(parts[1]);
}

directives[config.prefix + "once"] = function(el, val, vdom) {
  vdom.val = el.textContent;
  for(var child in vdom.children) {
    vdom.children[child].val = compileTemplate(vdom.children[child].val, self.$data);
  }
}

directives[config.prefix + "text"] = function(el, val, vdom) {
  el.textContent = val;
}

directives[config.prefix + "html"] = function(el, val, vdom) {
  el.innerHTML = val;
}

directives[config.prefix + "mask"] = function(el, val, vdom) {

}
<commit_msg>use call method<commit_after>/* ======= Default Directives ======= */
directives[config.prefix + "if"] = function(el, val, vdom) {
  var evaluated = new Function("return " + val);
  if(!evaluated()) {
    el.textContent = "";
  } else {
    el.textContent = compileTemplate(vdom.val, self.$data);
  }
}

directives[config.prefix + "show"] = function(el, val, vdom) {
  var evaluated = new Function("return " + val);
  if(!evaluated()) {
    el.style.display = 'none';
  } else {
    el.style.display = 'block';
  }
}

directives[config.prefix + "on"] = function(el, val, vdom) {
  var splitVal = val.split(":");
  var eventToCall = splitVal[0];
  var methodToCall = splitVal[1];
  el.addEventListener(eventToCall, function() {
    self.callMethod(methodToCall);
  });
  delete vdom.props[config.prefix + "on"];
}

directives[config.prefix + "model"] = function(el, val, vdom) {
  el.value = self.get(val);
  el.addEventListener("input", function() {
    self.set(val, el.value);
  });
  delete vdom.props[config.prefix + "model"];
}

directives[config.prefix + "for"] = function(el, val, vdom) {
  var parts = val.split(" in ");
  var alias = parts[0];
  var array = self.get(parts[1]);
}

directives[config.prefix + "once"] = function(el, val, vdom) {
  vdom.val = el.textContent;
  for(var child in vdom.children) {
    vdom.children[child].val = compileTemplate(vdom.children[child].val, self.$data);
  }
}

directives[config.prefix + "text"] = function(el, val, vdom) {
  el.textContent = val;
}

directives[config.prefix + "html"] = function(el, val, vdom) {
  el.innerHTML = val;
}

directives[config.prefix + "mask"] = function(el, val, vdom) {

}
<|endoftext|>


<commit_before>/**
 * this module contains the element unit styles related methods
 *
 * Credits:
 *   Some of the functionality is inspired by
 *     - Prototype (http://prototypejs.org)   Copyright (C) Sam Stephenson
 *     - MooTools  (http://mootools.net)      Copyright (C) Valerio Proietti
 *
 * Copyright (C) 2008-2009 Nikolay V. Nemshilov aka St. <nemshilov#gma-ilc-om>
 */
Element.addMethods({
  /**
   * assigns styles out of the hash to the element
   *
   * NOTE: the style keys might be camelized or dasherized, both cases should work
   *
   * @param Object styles list or String style name
   * @param String style value in case of the first param a string style name
   * @return Element self
   */
  setStyle: function(hash, value) {
    if (value) { var style = {}; style[hash] = value; hash = style; }
    else if(isString(hash)) {
      var style = {};
      hash.split(';').each(function(option) {
        var els = option.split(':').map('trim');
        if (els[0] && els[1]) {
          style[els[0]] = els[1];
        }
      });
      hash = style;
    }

    var c_key;
    for (var key in hash) {
      c_key = key.indexOf('-') != -1 ? key.camelize() : key;

      if (key == 'opacity') {
        this.setOpacity(hash[key]);
      } else if (key == 'float') {
        c_key = Browser.IE ? 'styleFloat' : 'cssFloat';
      }

      this.style[c_key] = hash[key];
    }

    return this;
  },

  /**
   * handles the opacity setting
   *
   * @param Float opacity value between 0 and 1
   * @return Element self
   */
  setOpacity: function(value) {
    var key = 'opacity';
    if (Browser.IE) {
      key = 'filter';
      value = 'alpha(opacity='+ value * 100 +')';
    }
    this.style[key] = value;
    return this;
  },

  /**
   * returns style of the element
   *
   * NOTE: will include the CSS level definitions
   *
   * @param String style key
   * @return String style value or null if not set
   */
  getStyle: function(key) {
    return this._getStyle(this.style, key) || this._getStyle(this.computedStyles(), key);
  },

  /**
   * returns the hash of computed styles for the element
   *
   * @return Object/CSSDefinition computed styles
   */
  computedStyles: function() {
    //     old IE,              IE8,                 W3C
    return this.currentStyle || this.runtimeStyle || this.ownerDocument.defaultView.getComputedStyle(this, null) || {};
  },

  // cleans up the style value
  _getStyle: function(style, key) {
    var value, key = key.camelize();

    switch (key) {
      case 'opacity':
        value = !Browser.IE ? style[key] :
          (((style['filter'] || '').match(/opacity=(\d+)/i) || ['', '100'])[1].toInt() / 100)+'';
        break;

      case 'float':
        key   = Browser.IE ? 'styleFloat' : 'cssFloat';

      default:
        if (style[key]) {
          value = style[key];
        } else {
          var values = $w('top right bottom left').map(function(name) {
            var tokens = key.underscored().split('_'); tokens.splice(1, 0, name);
            return style[tokens.join('_').camelize()];
          }).uniq();

          if (values.length == 1) {
            value = values[0];
          }
        }

        // Opera returns named colors with quotes
        if (value && Browser.Opera && key.match(/color/i)) {
          var match = value.match(/"(.+?)"/);
          value = match ? match[1] : value;
        }
    }

    return value ? value : null;
  },

  /**
   * checks if the element has the given class name
   *
   * @param String class name
   * @return boolean check result
   */
  hasClass: function(name) {
    return this.className && this.className.match(new RegExp('(^|\\s)'+ name + '(\\s|$)'));
  },

  /**
   * sets the whole class-name string for the element
   *
   * @param String class-name
   * @return Element self
   */
  setClass: function(class_name) {
    this.className = class_name;
    return this;
  },

  /**
   * adds the given class name to the element
   *
   * @param String class name
   * @return Element self
   */
  addClass: function(name) {
    if (!this.hasClass(name)) {
      this.className += (this.className ? ' ' : '') + name;
    }
    return this;
  },

  /**
   * removes the given class name
   *
   * @param String class name
   * @return Element self
   */
  removeClass: function(name) {
    this.className = this.className.replace(new RegExp('(^|\\s)' + name + '(?:\\s|$)'), '$1');
    return this;
  },

  /**
   * toggles the given class name on the element
   *
   * @param String class name
   * @return Element self
   */
   toggleClass: function(name) {
     return this[this.hasClass(name) ? 'removeClass' : 'addClass'](name);
   },

   /**
    * adds the given class-name to the element
    * and removes it from all the element siblings
    *
    * @param String class name
    * @return Element self
    */
   radioClass: function(name) {
     this.siblings().each('removeClass', name);
     return this.addClass(name);
   }
});<commit_msg>Faster add/has/remove Class methods implementation<commit_after>/**
 * this module contains the element unit styles related methods
 *
 * Credits:
 *   Some of the functionality is inspired by
 *     - Prototype (http://prototypejs.org)   Copyright (C) Sam Stephenson
 *     - MooTools  (http://mootools.net)      Copyright (C) Valerio Proietti
 *     - Dojo      (www.dojotoolkit.org)      Copyright (C) The Dojo Foundation
 *
 * Copyright (C) 2008-2009 Nikolay V. Nemshilov aka St. <nemshilov#gma-ilc-om>
 */
Element.addMethods({
  /**
   * assigns styles out of the hash to the element
   *
   * NOTE: the style keys might be camelized or dasherized, both cases should work
   *
   * @param Object styles list or String style name
   * @param String style value in case of the first param a string style name
   * @return Element self
   */
  setStyle: function(hash, value) {
    if (value) { var style = {}; style[hash] = value; hash = style; }
    else if(isString(hash)) {
      var style = {};
      hash.split(';').each(function(option) {
        var els = option.split(':').map('trim');
        if (els[0] && els[1]) {
          style[els[0]] = els[1];
        }
      });
      hash = style;
    }

    var c_key;
    for (var key in hash) {
      c_key = key.indexOf('-') != -1 ? key.camelize() : key;

      if (key == 'opacity') {
        this.setOpacity(hash[key]);
      } else if (key == 'float') {
        c_key = Browser.IE ? 'styleFloat' : 'cssFloat';
      }

      this.style[c_key] = hash[key];
    }

    return this;
  },

  /**
   * handles the opacity setting
   *
   * @param Float opacity value between 0 and 1
   * @return Element self
   */
  setOpacity: function(value) {
    var key = 'opacity';
    if (Browser.IE) {
      key = 'filter';
      value = 'alpha(opacity='+ value * 100 +')';
    }
    this.style[key] = value;
    return this;
  },

  /**
   * returns style of the element
   *
   * NOTE: will include the CSS level definitions
   *
   * @param String style key
   * @return String style value or null if not set
   */
  getStyle: function(key) {
    return this._getStyle(this.style, key) || this._getStyle(this.computedStyles(), key);
  },

  /**
   * returns the hash of computed styles for the element
   *
   * @return Object/CSSDefinition computed styles
   */
  computedStyles: function() {
    //     old IE,              IE8,                 W3C
    return this.currentStyle || this.runtimeStyle || this.ownerDocument.defaultView.getComputedStyle(this, null) || {};
  },

  // cleans up the style value
  _getStyle: function(style, key) {
    var value, key = key.camelize();

    switch (key) {
      case 'opacity':
        value = !Browser.IE ? style[key] :
          (((style['filter'] || '').match(/opacity=(\d+)/i) || ['', '100'])[1].toInt() / 100)+'';
        break;

      case 'float':
        key   = Browser.IE ? 'styleFloat' : 'cssFloat';

      default:
        if (style[key]) {
          value = style[key];
        } else {
          var values = $w('top right bottom left').map(function(name) {
            var tokens = key.underscored().split('_'); tokens.splice(1, 0, name);
            return style[tokens.join('_').camelize()];
          }).uniq();

          if (values.length == 1) {
            value = values[0];
          }
        }

        // Opera returns named colors with quotes
        if (value && Browser.Opera && key.match(/color/i)) {
          var match = value.match(/"(.+?)"/);
          value = match ? match[1] : value;
        }
    }

    return value ? value : null;
  },

  /**
   * checks if the element has the given class name
   *
   * @param String class name
   * @return boolean check result
   */
  hasClass: function(name) {
    return (' '+this.className+' ').indexOf(' '+name+' ') != -1;

    return this.className && this.className.match(new RegExp('(^|\\s)'+ name + '(\\s|$)'));
  },

  /**
   * sets the whole class-name string for the element
   *
   * @param String class-name
   * @return Element self
   */
  setClass: function(class_name) {
    this.className = class_name;
    return this;
  },

  /**
   * adds the given class name to the element
   *
   * @param String class name
   * @return Element self
   */
  addClass: function(name) {
    if ((' '+this.className+' ').indexOf(' '+name+' ') == -1) {
      this.className += (this.className ? ' ' : '') + name;
    }
    return this;
  },

  /**
   * removes the given class name
   *
   * @param String class name
   * @return Element self
   */
  removeClass: function(name) {
    this.className = (' '+this.className+' ').replace(' '+name+' ', ' ').trim();
    //this.className = this.className.replace(new RegExp('(^|\\s)' + name + '(?:\\s|$)'), '$1');
    return this;
  },

  /**
   * toggles the given class name on the element
   *
   * @param String class name
   * @return Element self
   */
   toggleClass: function(name) {
     return this[this.hasClass(name) ? 'removeClass' : 'addClass'](name);
   },

   /**
    * adds the given class-name to the element
    * and removes it from all the element siblings
    *
    * @param String class name
    * @return Element self
    */
   radioClass: function(name) {
     this.siblings().each('removeClass', name);
     return this.addClass(name);
   }
});<|endoftext|>


<commit_before>/*
Copyright 2019 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.

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 { walkDOMDepthFirst } from "./dom";
import { checkBlockNode } from "../HtmlUtils";
import {getPrimaryPermalinkEntity} from "../utils/permalinks/Permalinks";

function parseAtRoomMentions(text, partCreator) {
    const ATROOM = "@room";
    const parts = [];
    text.split(ATROOM).forEach((textPart, i, arr) => {
        if (textPart.length) {
            parts.push(partCreator.plain(textPart));
        }
        // it's safe to never append @room after the last textPart
        // as split will report an empty string at the end if
        // `text` ended in @room.
        const isLast = i === arr.length - 1;
        if (!isLast) {
            parts.push(partCreator.atRoomPill(ATROOM));
        }
    });
    return parts;
}

function parseLink(a, partCreator) {
    const {href} = a;
    const resourceId = getPrimaryPermalinkEntity(href); // The room/user ID
    const prefix = resourceId ? resourceId[0] : undefined; // First character of ID
    switch (prefix) {
        case "@":
            return partCreator.userPill(a.textContent, resourceId);
        case "#":
            return partCreator.roomPill(resourceId);
        default: {
            if (href === a.textContent) {
                return partCreator.plain(a.textContent);
            } else {
                return partCreator.plain(`[${a.textContent}](${href})`);
            }
        }
    }
}

function parseCodeBlock(n, partCreator) {
    const parts = [];
    const preLines = ("```\n" + n.textContent + "```").split("\n");
    preLines.forEach((l, i) => {
        parts.push(partCreator.plain(l));
        if (i < preLines.length - 1) {
            parts.push(partCreator.newline());
        }
    });
    return parts;
}

function parseHeader(el, partCreator) {
    const depth = parseInt(el.nodeName.substr(1), 10);
    return partCreator.plain("#".repeat(depth) + " ");
}

function parseElement(n, partCreator, lastNode, state) {
    switch (n.nodeName) {
        case "H1":
        case "H2":
        case "H3":
        case "H4":
        case "H5":
        case "H6":
            return parseHeader(n, partCreator);
        case "A":
            return parseLink(n, partCreator);
        case "BR":
            return partCreator.newline();
        case "EM":
            return partCreator.plain(`_${n.textContent}_`);
        case "STRONG":
            return partCreator.plain(`**${n.textContent}**`);
        case "PRE":
            return parseCodeBlock(n, partCreator);
        case "CODE":
            return partCreator.plain(`\`${n.textContent}\``);
        case "DEL":
            return partCreator.plain(`<del>${n.textContent}</del>`);
        case "LI": {
            const indent = "  ".repeat(state.listDepth - 1);
            if (n.parentElement.nodeName === "OL") {
                return partCreator.plain(`${indent}1. `);
            } else {
                return partCreator.plain(`${indent}- `);
            }
        }
        case "P": {
            if (lastNode) {
                return partCreator.newline();
            }
            break;
        }
        case "OL":
        case "UL":
            state.listDepth = (state.listDepth || 0) + 1;
        // es-lint-disable-next-line no-fallthrough
        default:
            // don't textify block nodes we'll decend into
            if (!checkDecendInto(n)) {
                return partCreator.plain(n.textContent);
            }
    }
}

function checkDecendInto(node) {
    switch (node.nodeName) {
        case "PRE":
            // a code block is textified in parseCodeBlock
            // as we don't want to preserve markup in it,
            // so no need to decend into it
            return false;
        default:
            return checkBlockNode(node);
    }
}

function checkIgnored(n) {
    if (n.nodeType === Node.TEXT_NODE) {
        // riot adds \n text nodes in a lot of places,
        // which should be ignored
        return n.nodeValue === "\n";
    } else if (n.nodeType === Node.ELEMENT_NODE) {
        return n.nodeName === "MX-REPLY";
    }
    return true;
}

const QUOTE_LINE_PREFIX = "> ";
function prefixQuoteLines(isFirstNode, parts, partCreator) {
    // a newline (to append a > to) wouldn't be added to parts for the first line
    // if there was no content before the BLOCKQUOTE, so handle that
    if (isFirstNode) {
        parts.splice(0, 0, partCreator.plain(QUOTE_LINE_PREFIX));
    }
    for (let i = 0; i < parts.length; i += 1) {
        if (parts[i].type === "newline") {
            parts.splice(i + 1, 0, partCreator.plain(QUOTE_LINE_PREFIX));
            i += 1;
        }
    }
}

function parseHtmlMessage(html, partCreator, isQuotedMessage) {
    // no nodes from parsing here should be inserted in the document,
    // as scripts in event handlers, etc would be executed then.
    // we're only taking text, so that is fine
    const rootNode = new DOMParser().parseFromString(html, "text/html").body;
    const parts = [];
    let lastNode;
    let inQuote = isQuotedMessage;
    const state = {};

    function onNodeEnter(n) {
        if (checkIgnored(n)) {
            return false;
        }
        if (n.nodeName === "BLOCKQUOTE") {
            inQuote = true;
        }

        const newParts = [];
        if (lastNode && (checkBlockNode(lastNode) || checkBlockNode(n))) {
            newParts.push(partCreator.newline());
        }

        if (n.nodeType === Node.TEXT_NODE) {
            newParts.push(...parseAtRoomMentions(n.nodeValue, partCreator));
        } else if (n.nodeType === Node.ELEMENT_NODE) {
            const parseResult = parseElement(n, partCreator, lastNode, state);
            if (parseResult) {
                if (Array.isArray(parseResult)) {
                    newParts.push(...parseResult);
                } else {
                    newParts.push(parseResult);
                }
            }
        }

        if (newParts.length && inQuote) {
            const isFirstPart = parts.length === 0;
            prefixQuoteLines(isFirstPart, newParts, partCreator);
        }

        parts.push(...newParts);

        const decend = checkDecendInto(n);
        // when not decending (like for PRE), onNodeLeave won't be called to set lastNode
        // so do that here.
        lastNode = decend ? null : n;
        return decend;
    }

    function onNodeLeave(n) {
        if (checkIgnored(n)) {
            return;
        }
        switch (n.nodeName) {
            case "BLOCKQUOTE":
                inQuote = false;
                break;
            case "OL":
            case "UL":
                state.listDepth -= 1;
                break;
        }
        lastNode = n;
    }

    walkDOMDepthFirst(rootNode, onNodeEnter, onNodeLeave);

    return parts;
}

export function parsePlainTextMessage(body, partCreator, isQuotedMessage) {
    const lines = body.split("\n");
    const parts = lines.reduce((parts, line, i) => {
        if (isQuotedMessage) {
            parts.push(partCreator.plain(QUOTE_LINE_PREFIX));
        }
        parts.push(...parseAtRoomMentions(line, partCreator));
        const isLast = i === lines.length - 1;
        if (!isLast) {
            parts.push(partCreator.newline());
        }
        return parts;
    }, []);
    return parts;
}

export function parseEvent(event, partCreator, {isQuotedMessage = false} = {}) {
    const content = event.getContent();
    let parts;
    if (content.format === "org.matrix.custom.html") {
        parts = parseHtmlMessage(content.formatted_body || "", partCreator, isQuotedMessage);
    } else {
        parts = parsePlainTextMessage(content.body || "", partCreator, isQuotedMessage);
    }
    if (content.msgtype === "m.emote") {
        parts.unshift(partCreator.plain("/me "));
    }
    return parts;
}
<commit_msg>Persist code block language when editing<commit_after>/*
Copyright 2019 New Vector Ltd
Copyright 2019 The Matrix.org Foundation C.I.C.

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 { walkDOMDepthFirst } from "./dom";
import { checkBlockNode } from "../HtmlUtils";
import {getPrimaryPermalinkEntity} from "../utils/permalinks/Permalinks";

function parseAtRoomMentions(text, partCreator) {
    const ATROOM = "@room";
    const parts = [];
    text.split(ATROOM).forEach((textPart, i, arr) => {
        if (textPart.length) {
            parts.push(partCreator.plain(textPart));
        }
        // it's safe to never append @room after the last textPart
        // as split will report an empty string at the end if
        // `text` ended in @room.
        const isLast = i === arr.length - 1;
        if (!isLast) {
            parts.push(partCreator.atRoomPill(ATROOM));
        }
    });
    return parts;
}

function parseLink(a, partCreator) {
    const {href} = a;
    const resourceId = getPrimaryPermalinkEntity(href); // The room/user ID
    const prefix = resourceId ? resourceId[0] : undefined; // First character of ID
    switch (prefix) {
        case "@":
            return partCreator.userPill(a.textContent, resourceId);
        case "#":
            return partCreator.roomPill(resourceId);
        default: {
            if (href === a.textContent) {
                return partCreator.plain(a.textContent);
            } else {
                return partCreator.plain(`[${a.textContent}](${href})`);
            }
        }
    }
}

function parseCodeBlock(n, partCreator) {
    const parts = [];
    let language = "";
    if (n.firstChild && n.firstChild.nodeName === "CODE") {
        for (const className of n.firstChild.classList) {
            if (className.startsWith("language-")) {
                language = className.substr("language-".length);
                break;
            }
        }
    }
    const preLines = ("```" + language + "\n" + n.textContent + "```").split("\n");
    preLines.forEach((l, i) => {
        parts.push(partCreator.plain(l));
        if (i < preLines.length - 1) {
            parts.push(partCreator.newline());
        }
    });
    return parts;
}

function parseHeader(el, partCreator) {
    const depth = parseInt(el.nodeName.substr(1), 10);
    return partCreator.plain("#".repeat(depth) + " ");
}

function parseElement(n, partCreator, lastNode, state) {
    switch (n.nodeName) {
        case "H1":
        case "H2":
        case "H3":
        case "H4":
        case "H5":
        case "H6":
            return parseHeader(n, partCreator);
        case "A":
            return parseLink(n, partCreator);
        case "BR":
            return partCreator.newline();
        case "EM":
            return partCreator.plain(`_${n.textContent}_`);
        case "STRONG":
            return partCreator.plain(`**${n.textContent}**`);
        case "PRE":
            return parseCodeBlock(n, partCreator);
        case "CODE":
            return partCreator.plain(`\`${n.textContent}\``);
        case "DEL":
            return partCreator.plain(`<del>${n.textContent}</del>`);
        case "LI": {
            const indent = "  ".repeat(state.listDepth - 1);
            if (n.parentElement.nodeName === "OL") {
                return partCreator.plain(`${indent}1. `);
            } else {
                return partCreator.plain(`${indent}- `);
            }
        }
        case "P": {
            if (lastNode) {
                return partCreator.newline();
            }
            break;
        }
        case "OL":
        case "UL":
            state.listDepth = (state.listDepth || 0) + 1;
        // es-lint-disable-next-line no-fallthrough
        default:
            // don't textify block nodes we'll decend into
            if (!checkDecendInto(n)) {
                return partCreator.plain(n.textContent);
            }
    }
}

function checkDecendInto(node) {
    switch (node.nodeName) {
        case "PRE":
            // a code block is textified in parseCodeBlock
            // as we don't want to preserve markup in it,
            // so no need to decend into it
            return false;
        default:
            return checkBlockNode(node);
    }
}

function checkIgnored(n) {
    if (n.nodeType === Node.TEXT_NODE) {
        // riot adds \n text nodes in a lot of places,
        // which should be ignored
        return n.nodeValue === "\n";
    } else if (n.nodeType === Node.ELEMENT_NODE) {
        return n.nodeName === "MX-REPLY";
    }
    return true;
}

const QUOTE_LINE_PREFIX = "> ";
function prefixQuoteLines(isFirstNode, parts, partCreator) {
    // a newline (to append a > to) wouldn't be added to parts for the first line
    // if there was no content before the BLOCKQUOTE, so handle that
    if (isFirstNode) {
        parts.splice(0, 0, partCreator.plain(QUOTE_LINE_PREFIX));
    }
    for (let i = 0; i < parts.length; i += 1) {
        if (parts[i].type === "newline") {
            parts.splice(i + 1, 0, partCreator.plain(QUOTE_LINE_PREFIX));
            i += 1;
        }
    }
}

function parseHtmlMessage(html, partCreator, isQuotedMessage) {
    // no nodes from parsing here should be inserted in the document,
    // as scripts in event handlers, etc would be executed then.
    // we're only taking text, so that is fine
    const rootNode = new DOMParser().parseFromString(html, "text/html").body;
    const parts = [];
    let lastNode;
    let inQuote = isQuotedMessage;
    const state = {};

    function onNodeEnter(n) {
        if (checkIgnored(n)) {
            return false;
        }
        if (n.nodeName === "BLOCKQUOTE") {
            inQuote = true;
        }

        const newParts = [];
        if (lastNode && (checkBlockNode(lastNode) || checkBlockNode(n))) {
            newParts.push(partCreator.newline());
        }

        if (n.nodeType === Node.TEXT_NODE) {
            newParts.push(...parseAtRoomMentions(n.nodeValue, partCreator));
        } else if (n.nodeType === Node.ELEMENT_NODE) {
            const parseResult = parseElement(n, partCreator, lastNode, state);
            if (parseResult) {
                if (Array.isArray(parseResult)) {
                    newParts.push(...parseResult);
                } else {
                    newParts.push(parseResult);
                }
            }
        }

        if (newParts.length && inQuote) {
            const isFirstPart = parts.length === 0;
            prefixQuoteLines(isFirstPart, newParts, partCreator);
        }

        parts.push(...newParts);

        const decend = checkDecendInto(n);
        // when not decending (like for PRE), onNodeLeave won't be called to set lastNode
        // so do that here.
        lastNode = decend ? null : n;
        return decend;
    }

    function onNodeLeave(n) {
        if (checkIgnored(n)) {
            return;
        }
        switch (n.nodeName) {
            case "BLOCKQUOTE":
                inQuote = false;
                break;
            case "OL":
            case "UL":
                state.listDepth -= 1;
                break;
        }
        lastNode = n;
    }

    walkDOMDepthFirst(rootNode, onNodeEnter, onNodeLeave);

    return parts;
}

export function parsePlainTextMessage(body, partCreator, isQuotedMessage) {
    const lines = body.split("\n");
    const parts = lines.reduce((parts, line, i) => {
        if (isQuotedMessage) {
            parts.push(partCreator.plain(QUOTE_LINE_PREFIX));
        }
        parts.push(...parseAtRoomMentions(line, partCreator));
        const isLast = i === lines.length - 1;
        if (!isLast) {
            parts.push(partCreator.newline());
        }
        return parts;
    }, []);
    return parts;
}

export function parseEvent(event, partCreator, {isQuotedMessage = false} = {}) {
    const content = event.getContent();
    let parts;
    if (content.format === "org.matrix.custom.html") {
        parts = parseHtmlMessage(content.formatted_body || "", partCreator, isQuotedMessage);
    } else {
        parts = parsePlainTextMessage(content.body || "", partCreator, isQuotedMessage);
    }
    if (content.msgtype === "m.emote") {
        parts.unshift(partCreator.plain("/me "));
    }
    return parts;
}
<|endoftext|>


<commit_before>let G = 500; //Gravitational constant
let EPSILON = 15;
let TAIL_LENGTH = 64;
let TAIL_VISIBILITY = 180;
let SIDE_MARGIN = 0.2; // For particles, as fraction of div width/height
let VERTICAL_MARGIN = 0.2;
let SHOT_RAD = 400;
let BACKGROUND = 30;
let PARTICLE_DRAW_SCALE = 6;

let ball = null;
let nuclei = []; //Static particles
let walls = [];
let target = null;
let shootx = 0;
let shooty = 0;
let div_height = 0;
let div_width = 0;
let level = 0;
var cnv;
//
// document.getElementById("reset").onclick = function() {ready_game()};

window.onkeydown = function(event){
    if(event.keyCode === 32) {
        event.preventDefault();
        document.getElementById("retry").click();
    }
    else if(event.keyCode === 13) {
        event.preventDefault();
        document.getElementById("new").click();
    }

};

window.onload = function() {
    div_width = document.getElementById("game").offsetWidth;
    div_height = document.getElementById("game").offsetHeight;
    console.log(div_height, div_width);
    new_game();
};

function setup() {
    cnv = createCanvas(div_width, div_height);
    cnv.style('display', 'block');
    cnv.parent("game");

    pos_img = loadImage("./assets/BLUE.png");
    neg_img = loadImage("./assets/YELLOW.png");
    el_img = loadImage("./assets/WHITE.png");
    target_img = loadImage("./assets/TARGET.png");

    // console.log(p.x +', '+p.y + ' v: ', p.vx);
    // console.log(i.x +', '+i.y + ', ' + i.intensity);
    // frameRate(1);
}
function new_game() {
    nuclei = [];
    walls = [];
    resizeCanvas(div_width, div_height);

    if(level >= levels.length) {
        end_game()
    }
    else {
        let level_indicator = document.getElementById("level");
        level_indicator.style.color = "#656565";
        level_indicator.style.fontSize = "40px";
        level_indicator.textContent = "LEVEL " + (level+1).toString();

        create_field_from_data(levels[level]);
        ball = new particle(50, div_height / 2, 1, 0, 0, 0, 0, true);
    }
}
function ready_game() {
    if (level >= levels.length) {
        level = 0;
        new_game();
    }
    else
        ball = new particle(50, div_height / 2, 1, 0, 0, 0, 0, true);
}

function end_game() {
    level++;
    let level_indicator = document.getElementById("level");
    level_indicator.textContent = "YOU WIN!";
    level_indicator.style.color = "red";
    level_indicator.style.fontSize = "60px";
}

function getRandomInt(min, max) {
    return Math.floor(Math.random() * Math.floor(max - min)) + min;
}

function reletive_to_absolute_position(x, y, intensity=1) {
    let left_extreme = div_width * SIDE_MARGIN;
    let right_extreme = div_width * (1 - SIDE_MARGIN);
    let up_extreme = div_height * VERTICAL_MARGIN;
    let bottom_extreme = div_height * (1 - VERTICAL_MARGIN);

    let x_abs = x * (right_extreme - left_extreme) + left_extreme;
    let y_abs = y * (bottom_extreme - up_extreme) + up_extreme;
    return [x_abs, y_abs, intensity];
}

function reletive_to_absolute_size(x, y) {
    let left_extreme = div_width * SIDE_MARGIN;
    let right_extreme = div_width * (1 - SIDE_MARGIN);
    let up_extreme = div_height * VERTICAL_MARGIN;
    let bottom_extreme = div_height * (1 - VERTICAL_MARGIN);

    let x_abs = x * (right_extreme - left_extreme);
    let y_abs = y * (bottom_extreme - up_extreme);
    return [x_abs, y_abs];
}

function create_field_from_data(data) {
    let x, y, intensity, width, height;

    for(let i = 0; i < data.nuclei.length; i++) {
        let curr = data.nuclei[i];
        [x, y, intensity] = reletive_to_absolute_position(curr.x, curr.y, curr.intensity);
        nuclei.push(new particle(x, y, intensity))
    }

    for(let i = 0; i < data.walls.length; i++) {
        let curr = data.walls[i];
        [x, y] = reletive_to_absolute_position(curr.x, curr.y);
        [width, height] = reletive_to_absolute_size(curr.width, curr.height);

        walls.push(new wall(x, y, width, height));
    }

    [x,y] = reletive_to_absolute_position(data.target.x, data.target.y);
    target = new particle(x, y);
}

function create_random_field(nuclei_num, target_num, min_intensity, max_intensity) {

    let this_field = {"nuclei":[], "target":null};

    let pos_num = getRandomInt(nuclei_num / 2, nuclei_num); //number of positive nuclei

    let left_extreme = div_width * SIDE_MARGIN;
    let right_extreme = div_width * (1 - SIDE_MARGIN);
    let up_extreme = div_height * VERTICAL_MARGIN;
    let bottom_extreme = div_height * (1 - VERTICAL_MARGIN);

    for(let i = 0; i < pos_num; i++) {
        let intensity = getRandomInt(min_intensity, max_intensity);
        let x = getRandomInt(left_extreme, right_extreme);
        let y = getRandomInt(up_extreme, bottom_extreme);
        let p = new particle(x, y, intensity);
        nuclei.push(p);

        this_field.nuclei.push([x, y, intensity]);
    }
    for(let i = 0; i < nuclei_num - pos_num - 1; i++) {
        let intensity = getRandomInt(min_intensity, max_intensity);
        let x = getRandomInt(left_extreme, right_extreme);
        let y = getRandomInt(up_extreme, bottom_extreme);
        let p = new particle(x, y, -intensity);
        nuclei.push(p);

        this_field.nuclei.push([x, y, intensity]);
    }

    for(let i = 0; i < target_num; i++) {
        let x = getRandomInt(left_extreme, right_extreme);
        let y = getRandomInt(up_extreme, bottom_extreme);
        target = new particle(x, y);

        this_field.target = [x, y];
    }

    console.log(JSON.stringify(this_field, null, 2));
}

function draw_nuclei() {
    for(let i = 0; i < nuclei.length; i++) {
        let size = PARTICLE_DRAW_SCALE * nuclei[i].intensity;
        // ellipse(nuclei[i].x, nuclei[i].y, size, size);
        if(nuclei[i].intensity > 0)
            image(pos_img, nuclei[i].x, nuclei[i].y, size, size);
        else
            image(neg_img, nuclei[i].x, nuclei[i].y, size, size)
    }
}

function collided_with_wall(x, y, i) {
    return x > walls[i].x && x < (walls[i].x + walls[i].width) &&
           y > walls[i].y && y < (walls[i].y + walls[i].height);

}
function update_ball() {
    if (ball.locked === false) {
        for (let j = 0; j < nuclei.length; j++) {
            ball.influenced_by(nuclei[j]);
        }
        ball.update();

        //Hit wall?
        for(let i = 0; i < walls.length; i++) {
            if(collided_with_wall(ball.x, ball.y, i))
                ball.die = true;
        }

        //Hit target?
        let distsq, dirx, diry;
        [distsq, [dirx, diry]] = ball.dist_from_particle(target);
        if(sqrt(distsq) < EPSILON) {
            ball.die = true;
            if(ball.die === true)
                end_game();
        }
    }
}

function draw_ball() {
    image(el_img, ball.x, ball.y, 10, 10);
}

function draw_target() {
    let size = PARTICLE_DRAW_SCALE * 6;
    image(target_img, target.x, target.y, size, size);
}

function draw_tails() {
    for(let j = 0; j < ball.history.length - 1; j++) {
        fill(Math.max(BACKGROUND, BACKGROUND + (j + 1) * (TAIL_VISIBILITY - BACKGROUND) / ball.history.length));
        ellipse(ball.history[j][0], ball.history[j][1], 3, 3);
    }
}

function draw_walls() {
    fill(193, 255, 193);
    stroke(200);
    rectMode(CORNER);
    for(let i = 0; i < walls.length; i++) {
        rect(walls[i].x, walls[i].y, walls[i].width, walls[i].height);
    }
}

function draw() {
    if(ball === null)
        return;
    console.log(ball.die);

    imageMode(CENTER);
    rectMode(CENTER);
    background(BACKGROUND);
    stroke(BACKGROUND);
    fill(255);
    smooth();

    if(ball.die === false)
        update_ball();
    draw_nuclei();
    draw_target();
    draw_ball();
    draw_tails();
    push();
    draw_walls();
    pop();

    if(ball.die === true && level < levels.length)
        ready_game();
    push();
    if(ball.locked === true)
        ready_shoot(ball);
    pop();

}

function dottedLine(x1, y1, x2, y2, dot_num) {
    for (let i = 0; i <= dot_num; i++) {
        let x = lerp(x1, x2, i/dot_num);
        let y = lerp(y1, y2, i/dot_num);
        point(x, y);
    }
}

function ready_shoot(p){
    stroke(250);
    let sx = mouseX;
    let sy = mouseY;
    let len = sqrt(pow(mouseX - p.x, 2) + pow(mouseY - p.y, 2));
    if(len > SHOT_RAD) {
        sx = (mouseX - p.x) * SHOT_RAD / len + p.x;
        sy = (mouseY - p.y) * SHOT_RAD / len + p.y;
    }
    dottedLine(p.x, p.y, sx, sy, 20);
    strokeWeight(.3);
    text(min(Math.round(len), SHOT_RAD).toString(), sx, sy + 20);
    // line(p.x, p.y, sx, sy);
    shootx = (sx - p.x) / 100;
    shooty = (sy - p.y) / 100;
}

function mousePressed() {
    if(ball.locked === true && mouseX < div_width) {
        ball.locked = false;
        ball.vx = shootx;
        ball.vy = shooty;
    }
}
let particle = function (x, y, intensity = 1, vx = 0, vy = 0, ax = 0, ay = 0, locked = false) {
    this.x = x;
    this.y = y;
    this.vx = vx;
    this.vy = vy;
    this.ax = ax;
    this.ay = ay;
    this.intensity = intensity;
    this.locked = locked;
    this.die = false;
    this.history = [];
    this.tick = 0;

    this.dist_from_particle = function(other) {
        // returns square of distance aith normalized direction
        let dx = other.x - this.x;
        let dy = other.y - this.y;
        let distsq = pow(dx, 2) + pow(dy, 2);
        let dirx = dx / sqrt(distsq);
        let diry = dy / sqrt(distsq);

        return [distsq, [dirx, diry]];
    };

    this.influenced_by = function(other) {
        let distsq, dirx, diry;
        [distsq, [dirx, diry]] = this.dist_from_particle(other);

        this.ax += dirx * G * this.intensity * other.intensity / distsq;
        this.ay += diry * G * this.intensity * other.intensity / distsq;

        if(sqrt(distsq) < EPSILON)
            this.die = true;
    };

    this.update = function() {
        this.tick++;
        this.vx += this.ax;
        this.vy += this.ay;

        if(this.tick % 3 === 0) {
            this.history.push([this.x, this.y]);
            if(this.history.length > TAIL_LENGTH)
                this.history.splice(0, 1);
        }
        this.x += this.vx;
        this.y += this.vy;

        this.ax = this.ay = 0;
    };
};

let wall = function (x, y, width, height) {
    this.x = x;
    this.y = y;
    this.width = width;
    this.height = height;
};<commit_msg>Bug fix in level loading<commit_after>let G = 500; //Gravitational constant
let EPSILON = 15;
let TAIL_LENGTH = 64;
let TAIL_VISIBILITY = 180;
let SIDE_MARGIN = 0.2; // For particles, as fraction of div width/height
let VERTICAL_MARGIN = 0.2;
let SHOT_RAD = 400;
let BACKGROUND = 30;
let PARTICLE_DRAW_SCALE = 6;

let ball = null;
let nuclei = []; //Static particles
let walls = [];
let target = null;
let shootx = 0;
let shooty = 0;
let div_height = 0;
let div_width = 0;
let level = 0;
var cnv;
//
// document.getElementById("reset").onclick = function() {ready_game()};

window.onkeydown = function(event){
    if(event.keyCode === 32) {
        event.preventDefault();
        document.getElementById("retry").click();
    }
    else if(event.keyCode === 13) {
        event.preventDefault();
        document.getElementById("new").click();
    }

};

window.onload = function() {
    div_width = document.getElementById("game").offsetWidth;
    div_height = document.getElementById("game").offsetHeight;
    console.log(div_height, div_width);
    new_game();
};

function setup() {
    cnv = createCanvas(div_width, div_height);
    cnv.style('display', 'block');
    cnv.parent("game");

    pos_img = loadImage("./assets/BLUE.png");
    neg_img = loadImage("./assets/YELLOW.png");
    el_img = loadImage("./assets/WHITE.png");
    target_img = loadImage("./assets/TARGET.png");

    // console.log(p.x +', '+p.y + ' v: ', p.vx);
    // console.log(i.x +', '+i.y + ', ' + i.intensity);
    // frameRate(1);
}
function new_game() {
    nuclei = [];
    walls = [];
    resizeCanvas(div_width, div_height);

    if(level >= levels.length) {
        end_game()
    }
    else {
        let level_indicator = document.getElementById("level");
        level_indicator.style.color = "#656565";
        level_indicator.style.fontSize = "40px";
        level_indicator.textContent = "LEVEL " + (level+1).toString();

        create_field_from_data(levels[level]);
        ball = new particle(50, div_height / 2, 1, 0, 0, 0, 0, true);
    }
}
function ready_game() {
    if (level >= levels.length) {
        level = 0;
        new_game();
    }
    else
        ball = new particle(50, div_height / 2, 1, 0, 0, 0, 0, true);
}
function win() {
    level++;
    new_game();
}
function end_game() {
    let level_indicator = document.getElementById("level");
    level_indicator.textContent = "YOU WIN!";
    level_indicator.style.color = "red";
    level_indicator.style.fontSize = "60px";
}

function getRandomInt(min, max) {
    return Math.floor(Math.random() * Math.floor(max - min)) + min;
}

function reletive_to_absolute_position(x, y, intensity=1) {
    let left_extreme = div_width * SIDE_MARGIN;
    let right_extreme = div_width * (1 - SIDE_MARGIN);
    let up_extreme = div_height * VERTICAL_MARGIN;
    let bottom_extreme = div_height * (1 - VERTICAL_MARGIN);

    let x_abs = x * (right_extreme - left_extreme) + left_extreme;
    let y_abs = y * (bottom_extreme - up_extreme) + up_extreme;
    return [x_abs, y_abs, intensity];
}

function reletive_to_absolute_size(x, y) {
    let left_extreme = div_width * SIDE_MARGIN;
    let right_extreme = div_width * (1 - SIDE_MARGIN);
    let up_extreme = div_height * VERTICAL_MARGIN;
    let bottom_extreme = div_height * (1 - VERTICAL_MARGIN);

    let x_abs = x * (right_extreme - left_extreme);
    let y_abs = y * (bottom_extreme - up_extreme);
    return [x_abs, y_abs];
}

function create_field_from_data(data) {
    let x, y, intensity, width, height;

    for(let i = 0; i < data.nuclei.length; i++) {
        let curr = data.nuclei[i];
        [x, y, intensity] = reletive_to_absolute_position(curr.x, curr.y, curr.intensity);
        nuclei.push(new particle(x, y, intensity))
    }

    for(let i = 0; i < data.walls.length; i++) {
        let curr = data.walls[i];
        [x, y] = reletive_to_absolute_position(curr.x, curr.y);
        [width, height] = reletive_to_absolute_size(curr.width, curr.height);

        walls.push(new wall(x, y, width, height));
    }

    [x,y] = reletive_to_absolute_position(data.target.x, data.target.y);
    target = new particle(x, y);
}

function create_random_field(nuclei_num, target_num, min_intensity, max_intensity) {

    let this_field = {"nuclei":[], "target":null};

    let pos_num = getRandomInt(nuclei_num / 2, nuclei_num); //number of positive nuclei

    let left_extreme = div_width * SIDE_MARGIN;
    let right_extreme = div_width * (1 - SIDE_MARGIN);
    let up_extreme = div_height * VERTICAL_MARGIN;
    let bottom_extreme = div_height * (1 - VERTICAL_MARGIN);

    for(let i = 0; i < pos_num; i++) {
        let intensity = getRandomInt(min_intensity, max_intensity);
        let x = getRandomInt(left_extreme, right_extreme);
        let y = getRandomInt(up_extreme, bottom_extreme);
        let p = new particle(x, y, intensity);
        nuclei.push(p);

        this_field.nuclei.push([x, y, intensity]);
    }
    for(let i = 0; i < nuclei_num - pos_num - 1; i++) {
        let intensity = getRandomInt(min_intensity, max_intensity);
        let x = getRandomInt(left_extreme, right_extreme);
        let y = getRandomInt(up_extreme, bottom_extreme);
        let p = new particle(x, y, -intensity);
        nuclei.push(p);

        this_field.nuclei.push([x, y, intensity]);
    }

    for(let i = 0; i < target_num; i++) {
        let x = getRandomInt(left_extreme, right_extreme);
        let y = getRandomInt(up_extreme, bottom_extreme);
        target = new particle(x, y);

        this_field.target = [x, y];
    }

    console.log(JSON.stringify(this_field, null, 2));
}

function draw_nuclei() {
    for(let i = 0; i < nuclei.length; i++) {
        let size = PARTICLE_DRAW_SCALE * nuclei[i].intensity;
        // ellipse(nuclei[i].x, nuclei[i].y, size, size);
        if(nuclei[i].intensity > 0)
            image(pos_img, nuclei[i].x, nuclei[i].y, size, size);
        else
            image(neg_img, nuclei[i].x, nuclei[i].y, size, size)
    }
}

function collided_with_wall(x, y, i) {
    return x > walls[i].x && x < (walls[i].x + walls[i].width) &&
           y > walls[i].y && y < (walls[i].y + walls[i].height);

}
function update_ball() {
    if (ball.locked === false) {
        for (let j = 0; j < nuclei.length; j++) {
            ball.influenced_by(nuclei[j]);
        }
        ball.update();

        //Hit wall?
        for(let i = 0; i < walls.length; i++) {
            if(collided_with_wall(ball.x, ball.y, i))
                ball.die = true;
        }

        //Hit target?
        let distsq, dirx, diry;
        [distsq, [dirx, diry]] = ball.dist_from_particle(target);
        if(sqrt(distsq) < EPSILON) {
            ball.die = true;
            if(ball.die === true)
                win();
        }
    }
}

function draw_ball() {
    image(el_img, ball.x, ball.y, 10, 10);
}

function draw_target() {
    let size = PARTICLE_DRAW_SCALE * 6;
    image(target_img, target.x, target.y, size, size);
}

function draw_tails() {
    for(let j = 0; j < ball.history.length - 1; j++) {
        fill(Math.max(BACKGROUND, BACKGROUND + (j + 1) * (TAIL_VISIBILITY - BACKGROUND) / ball.history.length));
        ellipse(ball.history[j][0], ball.history[j][1], 3, 3);
    }
}

function draw_walls() {
    fill(193, 255, 193);
    stroke(200);
    rectMode(CORNER);
    for(let i = 0; i < walls.length; i++) {
        rect(walls[i].x, walls[i].y, walls[i].width, walls[i].height);
    }
}

function draw() {
    if(ball === null)
        return;
    console.log(ball.die);

    imageMode(CENTER);
    rectMode(CENTER);
    background(BACKGROUND);
    stroke(BACKGROUND);
    fill(255);
    smooth();

    if(ball.die === false)
        update_ball();
    draw_nuclei();
    draw_target();
    draw_ball();
    draw_tails();
    push();
    draw_walls();
    pop();

    if(ball.die === true && level < levels.length)
        ready_game();
    push();
    if(ball.locked === true)
        ready_shoot(ball);
    pop();

}

function dottedLine(x1, y1, x2, y2, dot_num) {
    for (let i = 0; i <= dot_num; i++) {
        let x = lerp(x1, x2, i/dot_num);
        let y = lerp(y1, y2, i/dot_num);
        point(x, y);
    }
}

function ready_shoot(p){
    stroke(250);
    let sx = mouseX;
    let sy = mouseY;
    let len = sqrt(pow(mouseX - p.x, 2) + pow(mouseY - p.y, 2));
    if(len > SHOT_RAD) {
        sx = (mouseX - p.x) * SHOT_RAD / len + p.x;
        sy = (mouseY - p.y) * SHOT_RAD / len + p.y;
    }
    dottedLine(p.x, p.y, sx, sy, 20);
    strokeWeight(.3);
    text(min(Math.round(len), SHOT_RAD).toString(), sx, sy + 20);
    // line(p.x, p.y, sx, sy);
    shootx = (sx - p.x) / 100;
    shooty = (sy - p.y) / 100;
}

function mousePressed() {
    if(ball.locked === true && mouseX < div_width) {
        ball.locked = false;
        ball.vx = shootx;
        ball.vy = shooty;
    }
}
let particle = function (x, y, intensity = 1, vx = 0, vy = 0, ax = 0, ay = 0, locked = false) {
    this.x = x;
    this.y = y;
    this.vx = vx;
    this.vy = vy;
    this.ax = ax;
    this.ay = ay;
    this.intensity = intensity;
    this.locked = locked;
    this.die = false;
    this.history = [];
    this.tick = 0;

    this.dist_from_particle = function(other) {
        // returns square of distance aith normalized direction
        let dx = other.x - this.x;
        let dy = other.y - this.y;
        let distsq = pow(dx, 2) + pow(dy, 2);
        let dirx = dx / sqrt(distsq);
        let diry = dy / sqrt(distsq);

        return [distsq, [dirx, diry]];
    };

    this.influenced_by = function(other) {
        let distsq, dirx, diry;
        [distsq, [dirx, diry]] = this.dist_from_particle(other);

        this.ax += dirx * G * this.intensity * other.intensity / distsq;
        this.ay += diry * G * this.intensity * other.intensity / distsq;

        if(sqrt(distsq) < EPSILON)
            this.die = true;
    };

    this.update = function() {
        this.tick++;
        this.vx += this.ax;
        this.vy += this.ay;

        if(this.tick % 3 === 0) {
            this.history.push([this.x, this.y]);
            if(this.history.length > TAIL_LENGTH)
                this.history.splice(0, 1);
        }
        this.x += this.vx;
        this.y += this.vy;

        this.ax = this.ay = 0;
    };
};

let wall = function (x, y, width, height) {
    this.x = x;
    this.y = y;
    this.width = width;
    this.height = height;
};<|endoftext|>


<commit_before>"use strict";

var userHome = require("user-home");
var path = require("path");

const CACHE_DIR =
  process.env.FIREBASE_EMULATORS_PATH || path.join(userHome, ".cache", "firebase", "emulators");

const _emulators = {
  database: {
    name: "database",
    instance: null,
    port: 9000,
    stdout: null,
    cacheDir: CACHE_DIR,
    remoteUrl:
      "https://storage.googleapis.com/firebase-preview-drop/emulator/firebase-database-emulator-v3.5.0.jar",
    localPath: path.join(CACHE_DIR, "firebase-database-emulator-v3.5.0.jar"),
  },
  firestore: {
    name: "firestore",
    instance: null,
    port: 8080,
    stdout: null,
    cacheDir: CACHE_DIR,
    remoteUrl:
      "https://storage.googleapis.com/firebase-preview-drop/emulator/cloud-firestore-emulator-v1.2.0.jar",
    localPath: path.join(CACHE_DIR, "cloud-firestore-emulator-v1.2.0.jar"),
  },
};

const _commands = {
  database: {
    binary: "java",
    args: ["-jar", _emulators.database.localPath, "--port", _emulators.database.port],
  },
  firestore: {
    binary: "java",
    args: ["-jar", _emulators.firestore.localPath, "--port", _emulators.firestore.port],
  },
};

module.exports = {
  emulators: _emulators,
  commands: _commands,
};
<commit_msg>Workaround for lack of foreign language support in Firestore emulator<commit_after>"use strict";

var userHome = require("user-home");
var path = require("path");

const CACHE_DIR =
  process.env.FIREBASE_EMULATORS_PATH || path.join(userHome, ".cache", "firebase", "emulators");

const _emulators = {
  database: {
    name: "database",
    instance: null,
    port: 9000,
    stdout: null,
    cacheDir: CACHE_DIR,
    remoteUrl:
      "https://storage.googleapis.com/firebase-preview-drop/emulator/firebase-database-emulator-v3.5.0.jar",
    localPath: path.join(CACHE_DIR, "firebase-database-emulator-v3.5.0.jar"),
  },
  firestore: {
    name: "firestore",
    instance: null,
    port: 8080,
    stdout: null,
    cacheDir: CACHE_DIR,
    remoteUrl:
      "https://storage.googleapis.com/firebase-preview-drop/emulator/cloud-firestore-emulator-v1.2.0.jar",
    localPath: path.join(CACHE_DIR, "cloud-firestore-emulator-v1.2.0.jar"),
  },
};

const _commands = {
  database: {
    binary: "java",
    args: ["-jar", _emulators.database.localPath, "--port", _emulators.database.port],
  },
  firestore: {
    binary: "java",
    args: ["-Duser.language=en", "-jar", _emulators.firestore.localPath, "--port", _emulators.firestore.port],
  },
};

module.exports = {
  emulators: _emulators,
  commands: _commands,
};
<|endoftext|>


<commit_before>/** @babel */

import fs from 'fs'
import path from 'path'

import {Emitter, Disposable, CompositeDisposable} from 'event-kit'
import nsfw from 'nsfw'

import NativeWatcherRegistry from './native-watcher-registry'

// Private: Associate native watcher action type flags with descriptive String equivalents.
const ACTION_MAP = new Map([
  [nsfw.actions.MODIFIED, 'changed'],
  [nsfw.actions.CREATED, 'added'],
  [nsfw.actions.DELETED, 'deleted'],
  [nsfw.actions.RENAMED, 'renamed']
])

// Private: Possible states of a {NativeWatcher}.
export const WATCHER_STATE = {
  STOPPED: Symbol('stopped'),
  STARTING: Symbol('starting'),
  RUNNING: Symbol('running'),
  STOPPING: Symbol('stopping')
}

// Private: Interface with and normalize events from a native OS filesystem watcher.
class NativeWatcher {

  // Private: Initialize a native watcher on a path.
  //
  // Events will not be produced until {start()} is called.
  constructor (normalizedPath) {
    this.normalizedPath = normalizedPath
    this.emitter = new Emitter()

    this.watcher = null
    this.state = WATCHER_STATE.STOPPED
  }

  // Private: Begin watching for filesystem events.
  //
  // Has no effect if the watcher has already been started.
  async start () {
    if (this.state !== WATCHER_STATE.STOPPED) {
      return
    }
    this.state = WATCHER_STATE.STARTING

    this.watcher = await nsfw(
      this.normalizedPath,
      this.onEvents.bind(this),
      {
        debounceMS: 100,
        errorCallback: this.onError.bind(this)
      }
    )

    await this.watcher.start()

    this.state = WATCHER_STATE.RUNNING
    this.emitter.emit('did-start')
  }

  // Private: Return true if the underlying watcher is actively listening for filesystem events.
  isRunning () {
    return this.state === WATCHER_STATE.RUNNING
  }

  // Private: Register a callback to be invoked when the filesystem watcher has been initialized.
  //
  // Returns: A {Disposable} to revoke the subscription.
  onDidStart (callback) {
    return this.emitter.on('did-start', callback)
  }

  // Private: Register a callback to be invoked with normalized filesystem events as they arrive. Starts the watcher
  // automatically if it is not already running. The watcher will be stopped automatically when all subscribers
  // dispose their subscriptions.
  //
  // Returns: A {Disposable} to revoke the subscription.
  onDidChange (callback) {
    this.start()

    const sub = this.emitter.on('did-change', callback)
    return new Disposable(() => {
      sub.dispose()
      if (this.emitter.listenerCountForEventName('did-change') === 0) {
        this.stop()
      }
    })
  }

  // Private: Register a callback to be invoked when a {Watcher} should attach to a different {NativeWatcher}.
  //
  // Returns: A {Disposable} to revoke the subscription.
  onShouldDetach (callback) {
    return this.emitter.on('should-detach', callback)
  }

  // Private: Register a callback to be invoked when a {NativeWatcher} is about to be stopped.
  //
  // Returns: A {Disposable} to revoke the subscription.
  onWillStop (callback) {
    return this.emitter.on('will-stop', callback)
  }

  // Private: Register a callback to be invoked when the filesystem watcher has been stopped.
  //
  // Returns: A {Disposable} to revoke the subscription.
  onDidStop (callback) {
    return this.emitter.on('did-stop', callback)
  }

  // Private: Register a callback to be invoked with any errors reported from the watcher.
  //
  // Returns: A {Disposable} to revoke the subscription.
  onDidError (callback) {
    return this.emitter.on('did-error', callback)
  }

  // Private: Broadcast an `onShouldDetach` event to prompt any {Watcher} instances bound here to attach to a new
  // {NativeWatcher} instead.
  reattachTo (other) {
    this.emitter.emit('should-detach', other)
  }

  // Private: Stop the native watcher and release any operating system resources associated with it.
  //
  // Has no effect if the watcher is not running.
  async stop () {
    if (this.state !== WATCHER_STATE.RUNNING) {
      return
    }
    this.state = WATCHER_STATE.STOPPING
    this.emitter.emit('will-stop')

    await this.watcher.stop()
    this.state = WATCHER_STATE.STOPPED

    this.emitter.emit('did-stop')
  }

  // Private: Detach any event subscribers.
  dispose () {
    this.emitter.dispose()
  }

  // Private: Callback function invoked by the native watcher when a debounced group of filesystem events arrive.
  // Normalize and re-broadcast them to any subscribers.
  //
  // * `events` An Array of filesystem events.
  onEvents (events) {
    this.emitter.emit('did-change', events.map(event => {
      const type = ACTION_MAP.get(event.action) || `unexpected (${event.action})`
      const oldFileName = event.file || event.oldFile
      const newFileName = event.newFile
      const oldPath = path.join(event.directory, oldFileName)
      const newPath = newFileName && path.join(event.directory, newFileName)

      return {oldPath, newPath, type}
    }))
  }

  // Private: Callback function invoked by the native watcher when an error occurs.
  //
  // * `err` The native filesystem error.
  onError (err) {
    this.emitter.emit('did-error', err)
  }
}

class Watcher {
  constructor (watchedPath, nativeWatcherRegistry) {
    this.watchedPath = watchedPath
    this.nativeWatcherRegistry = nativeWatcherRegistry

    this.normalizedPath = null
    this.native = null
    this.changeCallbacks = new Map()

    this.normalizedPathPromise = new Promise((resolve, reject) => {
      fs.realpath(watchedPath, (err, real) => {
        if (err) {
          reject(err)
          return
        }

        this.normalizedPath = real
        resolve(real)
      })
    })

    this.attachedPromise = new Promise(resolve => {
      this.resolveAttachedPromise = resolve
    })
    this.startPromise = new Promise(resolve => {
      this.resolveStartPromise = resolve
    })

    this.emitter = new Emitter()
    this.subs = new CompositeDisposable()
  }

  getNormalizedPathPromise () {
    return this.normalizedPathPromise
  }

  getAttachedPromise () {
    return this.attachedPromise
  }

  getStartPromise () {
    return this.startPromise
  }

  onDidChange (callback) {
    if (this.native) {
      const sub = this.native.onDidChange(events => this.onNativeEvents(events, callback))
      this.changeCallbacks.set(callback, sub)

      this.native.start()
    } else {
      // Attach to a new native listener and retry
      this.nativeWatcherRegistry.attach(this).then(() => {
        this.onDidChange(callback)
      })
    }

    return new Disposable(() => {
      const sub = this.changeCallbacks.get(callback)
      this.changeCallbacks.delete(callback)
      sub.dispose()
    })
  }

  onDidError (callback) {
    return this.emitter.on('did-error', callback)
  }

  attachToNative (native) {
    this.subs.dispose()
    this.native = native

    if (native.isRunning()) {
      this.resolveStartPromise()
    } else {
      this.subs.add(native.onDidStart(() => {
        this.resolveStartPromise()
      }))
    }

    // Transfer any native event subscriptions to the new NativeWatcher.
    for (const [callback, formerSub] of this.changeCallbacks) {
      const newSub = native.onDidChange(events => this.onNativeEvents(events, callback))
      this.changeCallbacks.set(callback, newSub)
      formerSub.dispose()
    }

    this.subs.add(native.onDidError(err => {
      this.emitter.emit('did-error', err)
    }))

    this.subs.add(native.onShouldDetach(replacement => {
      if (replacement !== native) {
        this.attachToNative(replacement)
      }
    }))

    this.subs.add(native.onWillStop(() => {
      this.subs.dispose()
      this.native = null
    }))

    this.resolveAttachedPromise()
  }

  onNativeEvents (events, callback) {
    // TODO does event.oldPath resolve symlinks?
    const filtered = events.filter(event => event.oldPath.startsWith(this.normalizedPath))

    if (filtered.length > 0) {
      callback(filtered)
    }
  }

  dispose () {
    for (const sub of this.changeCallbacks.values()) {
      sub.dispose()
    }

    this.emitter.dispose()
    this.subs.dispose()
  }
}

export default class FileSystemManager {
  constructor () {
    this.liveWatchers = new Set()

    this.nativeWatchers = new NativeWatcherRegistry(
      normalizedPath => {
        const nativeWatcher = new NativeWatcher(normalizedPath)

        this.liveWatchers.add(nativeWatcher)
        const sub = nativeWatcher.onWillStop(() => {
          this.liveWatchers.delete(nativeWatcher)
          sub.dispose()
        })

        return nativeWatcher
      }
    )
  }

  getWatcher (rootPath) {
    return new Watcher(rootPath, this.nativeWatchers)
  }
}

// Private: Return a Promise that resolves when all {NativeWatcher} instances associated with a FileSystemManager
// have stopped listening. This is useful for `afterEach()` blocks in unit tests.
export function stopAllWatchers (manager) {
  return Promise.all(
    Array.from(manager.liveWatchers, watcher => watcher.stop())
  )
}
<commit_msg>(Untested) work to adapt to the registry API changes<commit_after>/** @babel */

import fs from 'fs'
import path from 'path'

import {Emitter, Disposable, CompositeDisposable} from 'event-kit'
import nsfw from 'nsfw'

import NativeWatcherRegistry from './native-watcher-registry'

// Private: Associate native watcher action type flags with descriptive String equivalents.
const ACTION_MAP = new Map([
  [nsfw.actions.MODIFIED, 'changed'],
  [nsfw.actions.CREATED, 'added'],
  [nsfw.actions.DELETED, 'deleted'],
  [nsfw.actions.RENAMED, 'renamed']
])

// Private: Possible states of a {NativeWatcher}.
export const WATCHER_STATE = {
  STOPPED: Symbol('stopped'),
  STARTING: Symbol('starting'),
  RUNNING: Symbol('running'),
  STOPPING: Symbol('stopping')
}

// Private: Interface with and normalize events from a native OS filesystem watcher.
class NativeWatcher {

  // Private: Initialize a native watcher on a path.
  //
  // Events will not be produced until {start()} is called.
  constructor (normalizedPath) {
    this.normalizedPath = normalizedPath
    this.emitter = new Emitter()

    this.watcher = null
    this.state = WATCHER_STATE.STOPPED
  }

  // Private: Begin watching for filesystem events.
  //
  // Has no effect if the watcher has already been started.
  async start () {
    if (this.state !== WATCHER_STATE.STOPPED) {
      return
    }
    this.state = WATCHER_STATE.STARTING

    this.watcher = await nsfw(
      this.normalizedPath,
      this.onEvents.bind(this),
      {
        debounceMS: 100,
        errorCallback: this.onError.bind(this)
      }
    )

    await this.watcher.start()

    this.state = WATCHER_STATE.RUNNING
    this.emitter.emit('did-start')
  }

  // Private: Return true if the underlying watcher is actively listening for filesystem events.
  isRunning () {
    return this.state === WATCHER_STATE.RUNNING
  }

  // Private: Register a callback to be invoked when the filesystem watcher has been initialized.
  //
  // Returns: A {Disposable} to revoke the subscription.
  onDidStart (callback) {
    return this.emitter.on('did-start', callback)
  }

  // Private: Register a callback to be invoked with normalized filesystem events as they arrive. Starts the watcher
  // automatically if it is not already running. The watcher will be stopped automatically when all subscribers
  // dispose their subscriptions.
  //
  // Returns: A {Disposable} to revoke the subscription.
  onDidChange (callback) {
    this.start()

    const sub = this.emitter.on('did-change', callback)
    return new Disposable(() => {
      sub.dispose()
      if (this.emitter.listenerCountForEventName('did-change') === 0) {
        this.stop()
      }
    })
  }

  // Private: Register a callback to be invoked when a {Watcher} should attach to a different {NativeWatcher}.
  //
  // Returns: A {Disposable} to revoke the subscription.
  onShouldDetach (callback) {
    return this.emitter.on('should-detach', callback)
  }

  // Private: Register a callback to be invoked when a {NativeWatcher} is about to be stopped.
  //
  // Returns: A {Disposable} to revoke the subscription.
  onWillStop (callback) {
    return this.emitter.on('will-stop', callback)
  }

  // Private: Register a callback to be invoked when the filesystem watcher has been stopped.
  //
  // Returns: A {Disposable} to revoke the subscription.
  onDidStop (callback) {
    return this.emitter.on('did-stop', callback)
  }

  // Private: Register a callback to be invoked with any errors reported from the watcher.
  //
  // Returns: A {Disposable} to revoke the subscription.
  onDidError (callback) {
    return this.emitter.on('did-error', callback)
  }

  // Private: Broadcast an `onShouldDetach` event to prompt any {Watcher} instances bound here to attach to a new
  // {NativeWatcher} instead.
  //
  // * `replacement` the new {NativeWatcher} instance that a live {Watcher} instance should reattach to instead.
  // * `watchedPath` absolute path watched by the new {NativeWatcher}.
  reattachTo (replacement, watchedPath) {
    this.emitter.emit('should-detach', {replacement, watchedPath})
  }

  // Private: Stop the native watcher and release any operating system resources associated with it.
  //
  // Has no effect if the watcher is not running.
  async stop () {
    if (this.state !== WATCHER_STATE.RUNNING) {
      return
    }
    this.state = WATCHER_STATE.STOPPING
    this.emitter.emit('will-stop')

    await this.watcher.stop()
    this.state = WATCHER_STATE.STOPPED

    this.emitter.emit('did-stop')
  }

  // Private: Detach any event subscribers.
  dispose () {
    this.emitter.dispose()
  }

  // Private: Callback function invoked by the native watcher when a debounced group of filesystem events arrive.
  // Normalize and re-broadcast them to any subscribers.
  //
  // * `events` An Array of filesystem events.
  onEvents (events) {
    this.emitter.emit('did-change', events.map(event => {
      const type = ACTION_MAP.get(event.action) || `unexpected (${event.action})`
      const oldFileName = event.file || event.oldFile
      const newFileName = event.newFile
      const oldPath = path.join(event.directory, oldFileName)
      const newPath = newFileName && path.join(event.directory, newFileName)

      return {oldPath, newPath, type}
    }))
  }

  // Private: Callback function invoked by the native watcher when an error occurs.
  //
  // * `err` The native filesystem error.
  onError (err) {
    this.emitter.emit('did-error', err)
  }
}

class Watcher {
  constructor (watchedPath, nativeWatcherRegistry) {
    this.watchedPath = watchedPath
    this.nativeWatcherRegistry = nativeWatcherRegistry

    this.normalizedPath = null
    this.native = null
    this.changeCallbacks = new Map()

    this.normalizedPathPromise = new Promise((resolve, reject) => {
      fs.realpath(watchedPath, (err, real) => {
        if (err) {
          reject(err)
          return
        }

        this.normalizedPath = real
        resolve(real)
      })
    })

    this.attachedPromise = new Promise(resolve => {
      this.resolveAttachedPromise = resolve
    })
    this.startPromise = new Promise(resolve => {
      this.resolveStartPromise = resolve
    })

    this.emitter = new Emitter()
    this.subs = new CompositeDisposable()
  }

  getNormalizedPathPromise () {
    return this.normalizedPathPromise
  }

  getAttachedPromise () {
    return this.attachedPromise
  }

  getStartPromise () {
    return this.startPromise
  }

  onDidChange (callback) {
    if (this.native) {
      const sub = this.native.onDidChange(events => this.onNativeEvents(events, callback))
      this.changeCallbacks.set(callback, sub)

      this.native.start()
    } else {
      // Attach to a new native listener and retry
      this.nativeWatcherRegistry.attach(this).then(() => {
        this.onDidChange(callback)
      })
    }

    return new Disposable(() => {
      const sub = this.changeCallbacks.get(callback)
      this.changeCallbacks.delete(callback)
      sub.dispose()
    })
  }

  onDidError (callback) {
    return this.emitter.on('did-error', callback)
  }

  attachToNative (native) {
    this.subs.dispose()
    this.native = native

    if (native.isRunning()) {
      this.resolveStartPromise()
    } else {
      this.subs.add(native.onDidStart(() => {
        this.resolveStartPromise()
      }))
    }

    // Transfer any native event subscriptions to the new NativeWatcher.
    for (const [callback, formerSub] of this.changeCallbacks) {
      const newSub = native.onDidChange(events => this.onNativeEvents(events, callback))
      this.changeCallbacks.set(callback, newSub)
      formerSub.dispose()
    }

    this.subs.add(native.onDidError(err => {
      this.emitter.emit('did-error', err)
    }))

    this.subs.add(native.onShouldDetach(({replacement, watchedPath}) => {
      if (replacement !== native && this.normalizedPath.startsWith(watchedPath)) {
        this.attachToNative(replacement)
      }
    }))

    this.subs.add(native.onWillStop(() => {
      this.subs.dispose()
      this.native = null
    }))

    this.resolveAttachedPromise()
  }

  onNativeEvents (events, callback) {
    // TODO does event.oldPath resolve symlinks?
    const filtered = events.filter(event => event.oldPath.startsWith(this.normalizedPath))

    if (filtered.length > 0) {
      callback(filtered)
    }
  }

  dispose () {
    for (const sub of this.changeCallbacks.values()) {
      sub.dispose()
    }

    this.emitter.dispose()
    this.subs.dispose()
  }
}

export default class FileSystemManager {
  constructor () {
    this.liveWatchers = new Set()

    this.nativeWatchers = new NativeWatcherRegistry(
      normalizedPath => {
        const nativeWatcher = new NativeWatcher(normalizedPath)

        this.liveWatchers.add(nativeWatcher)
        const sub = nativeWatcher.onWillStop(() => {
          this.liveWatchers.delete(nativeWatcher)
          sub.dispose()
        })

        return nativeWatcher
      }
    )
  }

  getWatcher (rootPath) {
    return new Watcher(rootPath, this.nativeWatchers)
  }
}

// Private: Return a Promise that resolves when all {NativeWatcher} instances associated with a FileSystemManager
// have stopped listening. This is useful for `afterEach()` blocks in unit tests.
export function stopAllWatchers (manager) {
  return Promise.all(
    Array.from(manager.liveWatchers, watcher => watcher.stop())
  )
}
<|endoftext|>