<commit_before>(function() {
  const out$ = typeof exports != 'undefined' && exports || typeof define != 'undefined' && {} || this || window;
  if (typeof define !== 'undefined') define('save-svg-as-png', [], () => out$);
  out$.default = out$;

  const xmlNs = 'http://www.w3.org/2000/xmlns/';
  const xhtmlNs = 'http://www.w3.org/1999/xhtml';
  const svgNs = 'http://www.w3.org/2000/svg';
  const doctype = '<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [<!ENTITY nbsp " ">]>';
  const urlRegex = /url\(["']?(.+?)["']?\)/;
  const fontFormats = {
    woff2: 'font/woff2',
    woff: 'font/woff',
    otf: 'application/x-font-opentype',
    ttf: 'application/x-font-ttf',
    eot: 'application/vnd.ms-fontobject',
    sfnt: 'application/font-sfnt',
    svg: 'image/svg+xml'
  };

  const isElement = obj => obj instanceof HTMLElement || obj instanceof SVGElement;
  const requireDomNode = el => {
    if (!isElement(el)) throw new Error(`an HTMLElement or SVGElement is required; got ${el}`);
  };
  const requireDomNodePromise = el =>
    new Promise((resolve, reject) => {
      if (isElement(el)) resolve(el)
      else reject(new Error(`an HTMLElement or SVGElement is required; got ${el}`));
    })
  const isExternal = url => url && url.lastIndexOf('http',0) === 0 && url.lastIndexOf(window.location.host) === -1;

  const getFontMimeTypeFromUrl = fontUrl => {
    const formats = Object.keys(fontFormats)
      .filter(extension => fontUrl.indexOf(`.${extension}`) > 0)
      .map(extension => fontFormats[extension]);
    if (formats) return formats[0];
    console.error(`Unknown font format for ${fontUrl}. Fonts may not be working correctly.`);
    return 'application/octet-stream';
  };

  const arrayBufferToBase64 = buffer => {
    let binary = '';
    const bytes = new Uint8Array(buffer);
    for (let i = 0; i < bytes.byteLength; i++) binary += String.fromCharCode(bytes[i]);
    return window.btoa(binary);
  }

  const getDimension = (el, clone, dim) => {
    const v =
      (el.viewBox && el.viewBox.baseVal && el.viewBox.baseVal[dim]) ||
      (clone.getAttribute(dim) !== null && !clone.getAttribute(dim).match(/%$/) && parseInt(clone.getAttribute(dim))) ||
      el.getBoundingClientRect()[dim] ||
      parseInt(clone.style[dim]) ||
      parseInt(window.getComputedStyle(el).getPropertyValue(dim));
    return typeof v === 'undefined' || v === null || isNaN(parseFloat(v)) ? 0 : v;
  };

  const getDimensions = (el, clone, width, height) => {
    if (el.tagName === 'svg') return {
      width: width || getDimension(el, clone, 'width'),
      height: height || getDimension(el, clone, 'height')
    };
    else if (el.getBBox) {
      const {x, y, width, height} = el.getBBox();
      return {
        width: x + width,
        height: y + height
      };
    }
  };

  const reEncode = data =>
    decodeURIComponent(
      encodeURIComponent(data)
        .replace(/%([0-9A-F]{2})/g, (match, p1) => {
          const c = String.fromCharCode(`0x${p1}`);
          return c === '%' ? '%25' : c;
        })
    );

  const uriToBlob = uri => {
    const byteString = window.atob(uri.split(',')[1]);
    const mimeString = uri.split(',')[0].split(':')[1].split(';')[0]
    const buffer = new ArrayBuffer(byteString.length);
    const intArray = new Uint8Array(buffer);
    for (let i = 0; i < byteString.length; i++) {
      intArray[i] = byteString.charCodeAt(i);
    }
    return new Blob([buffer], {type: mimeString});
  };

  const query = (el, selector) => {
    if (!selector) return;
    try {
      return el.querySelector(selector) || el.parentNode && el.parentNode.querySelector(selector);
    } catch(err) {
      console.warn(`Invalid CSS selector "${selector}"`, err);
    }
  };

  const detectCssFont = (rule, href) => {
    // Match CSS font-face rules to external links.
    // @font-face {
    //   src: local('Abel'), url(https://fonts.gstatic.com/s/abel/v6/UzN-iejR1VoXU2Oc-7LsbvesZW2xOQ-xsNqO47m55DA.woff2);
    // }
    const match = rule.cssText.match(urlRegex);
    const url = (match && match[1]) || '';
    if (!url || url.match(/^data:/) || url === 'about:blank') return;
    const fullUrl =
      url.startsWith('../') ? `${href}/../${url}`
      : url.startsWith('./') ? `${href}/.${url}`
      : url;
    return {
      text: rule.cssText,
      format: getFontMimeTypeFromUrl(fullUrl),
      url: fullUrl
    };
  };

  const inlineImages = el => Promise.all(
    Array.from(el.querySelectorAll('image')).map(image => {
      let href = image.getAttributeNS('http://www.w3.org/1999/xlink', 'href') || image.getAttribute('href');
      if (!href) return Promise.resolve(null);
      if (isExternal(href)) {
        href += (href.indexOf('?') === -1 ? '?' : '&') + 't=' + new Date().valueOf();
      }
      return new Promise((resolve, reject) => {
        const canvas = document.createElement('canvas');
        const img = new Image();
        img.crossOrigin = 'anonymous';
        img.src = href;
        img.onerror = () => reject(new Error(`Could not load ${href}`));
        img.onload = () => {
          canvas.width = img.width;
          canvas.height = img.height;
          canvas.getContext('2d').drawImage(img, 0, 0);
          image.setAttributeNS('http://www.w3.org/1999/xlink', 'href', canvas.toDataURL('image/png'));
          resolve(true);
        };
      });
    })
  );

  const cachedFonts = {};
  const inlineFonts = fonts => Promise.all(
    fonts.map(font =>
      new Promise((resolve, reject) => {
        if (cachedFonts[font.url]) return resolve(cachedFonts[font.url]);

        const req = new XMLHttpRequest();
        req.addEventListener('load', () => {
          // TODO: it may also be worth it to wait until fonts are fully loaded before
          // attempting to rasterize them. (e.g. use https://developer.mozilla.org/en-US/docs/Web/API/FontFaceSet)
          const fontInBase64 = arrayBufferToBase64(req.response);
          const fontUri = font.text.replace(urlRegex, `url("data:${font.format};base64,${fontInBase64}")`)+'\n';
          cachedFonts[font.url] = fontUri;
          resolve(fontUri);
        });
        req.addEventListener('error', e => {
          console.warn(`Failed to load font from: ${font.url}`, e);
          cachedFonts[font.url] = null;
          resolve(null);
        });
        req.addEventListener('abort', e => {
          console.warn(`Aborted loading font from: ${font.url}`, e);
          resolve(null);
        });
        req.open('GET', font.url);
        req.responseType = 'arraybuffer';
        req.send();
      })
    )
  ).then(fontCss => fontCss.filter(x => x).join(''));

  let cachedRules = null;
  const styleSheetRules = () => {
    if (cachedRules) return cachedRules;
    return cachedRules = Array.from(document.styleSheets).map(sheet => {
      try {
        return {rules: sheet.cssRules, href: sheet.href};
      } catch (e) {
        console.warn(`Stylesheet could not be loaded: ${sheet.href}`, e);
        return {};
      }
    });
  };

  const inlineCss = (el, options) => {
    const {
      selectorRemap,
      modifyStyle,
      modifyCss,
      fonts
    } = options || {};
    const generateCss = modifyCss || ((selector, properties) => {
      const sel = selectorRemap ? selectorRemap(selector) : selector;
      const props = modifyStyle ? modifyStyle(properties) : properties;
      return `${sel}{${props}}\n`;
    });
    const css = [];
    const detectFonts = typeof fonts === 'undefined';
    const fontList = fonts || [];
    styleSheetRules().forEach(({rules, href}) => {
      if (!rules) return;
      Array.from(rules).forEach(rule => {
        if (typeof rule.style != 'undefined') {
          if (query(el, rule.selectorText)) css.push(generateCss(rule.selectorText, rule.style.cssText));
          else if (detectFonts && rule.cssText.match(/^@font-face/)) {
            const font = detectCssFont(rule, href);
            if (font) fontList.push(font);
          } else css.push(rule.cssText);
        }
      });
    });

    return inlineFonts(fontList).then(fontCss => css.join('\n') + fontCss);
  };

  out$.prepareSvg = (el, options, done) => {
    requireDomNode(el);
    const {
      left = 0,
      top = 0,
      width: w,
      height: h,
      scale = 1,
      responsive = false,
    } = options || {};

    return inlineImages(el).then(() => {
      let clone = el.cloneNode(true);
      clone.style.backgroundColor = (options || {}).backgroundColor || el.style.backgroundColor;
      const {width, height} = getDimensions(el, clone, w, h);

      if (el.tagName !== 'svg') {
        if (el.getBBox) {
          if (clone.getAttribute('transform') != null) {
            clone.setAttribute('transform', clone.getAttribute('transform').replace(/translate\(.*?\)/, ''));
          }
          const svg = document.createElementNS('http://www.w3.org/2000/svg','svg');
          svg.appendChild(clone);
          clone = svg;
        } else {
          console.error('Attempted to render non-SVG element', el);
          return;
        }
      }

      clone.setAttribute('version', '1.1');
      clone.setAttribute('viewBox', [left, top, width, height].join(' '));
      if (!clone.getAttribute('xmlns')) clone.setAttributeNS(xmlNs, 'xmlns', svgNs);
      if (!clone.getAttribute('xmlns:xlink')) clone.setAttributeNS(xmlNs, 'xmlns:xlink', 'http://www.w3.org/1999/xlink');

      if (responsive) {
        clone.removeAttribute('width');
        clone.removeAttribute('height');
        clone.setAttribute('preserveAspectRatio', 'xMinYMin meet');
      } else {
        clone.setAttribute('width', width * scale);
        clone.setAttribute('height', height * scale);
      }

      Array.from(clone.querySelectorAll('foreignObject > *')).forEach(foreignObject => {
        foreignObject.setAttributeNS(xmlNs, 'xmlns', foreignObject.tagName === 'svg' ? svgNs : xhtmlNs);
      });

      return inlineCss(el, options).then(css => {
        const style = document.createElement('style');
        style.setAttribute('type', 'text/css');
        style.innerHTML = `<![CDATA[\n${css}\n]]>`;

        const defs = document.createElement('defs');
        defs.appendChild(style);
        clone.insertBefore(defs, clone.firstChild);

        const outer = document.createElement('div');
        outer.appendChild(clone);
        const src = outer.innerHTML.replace(/NS\d+:href/gi, 'xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href');

        if (typeof done === 'function') done(src, width, height);
        else return {src, width, height};
      });
    });
  };

  out$.svgAsDataUri = (el, options, done) => {
    requireDomNode(el);
    return out$.prepareSvg(el, options)
      .then(({src, width, height}) => {
          const svgXml = `data:image/svg+xml;base64,${window.btoa(reEncode(doctype+src))}`;
          if (typeof done === 'function') {
              done(svgXml, width, height);
          }
          return svgXml;
      });
  };

  out$.svgAsPngUri = (el, options, done) => {
    requireDomNode(el);
    const {
      encoderType = 'image/png',
      encoderOptions = 0.8,
      canvg
    } = options || {};

    const convertToPng = ({src, width, height}) => {
      const canvas = document.createElement('canvas');
      const context = canvas.getContext('2d');
      const pixelRatio = window.devicePixelRatio || 1;

      canvas.width = width * pixelRatio;
      canvas.height = height * pixelRatio;
      canvas.style.width = `${canvas.width}px`;
      canvas.style.height = `${canvas.height}px`;
      context.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);

      if (canvg) canvg(canvas, src);
      else context.drawImage(src, 0, 0);

      let png;
      try {
        png = canvas.toDataURL(encoderType, encoderOptions);
      } catch (e) {
        if ((typeof SecurityError !== 'undefined' && e instanceof SecurityError) || e.name === 'SecurityError') {
          console.error('Rendered SVG images cannot be downloaded in this browser.');
          return;
        } else throw e;
      }
      if (typeof done === 'function') done(png, canvas.width, canvas.height);
      return Promise.resolve(png);
    }

    if (canvg) return out$.prepareSvg(el, options).then(convertToPng);
    else return out$.svgAsDataUri(el, options).then(uri => {
      return new Promise((resolve, reject) => {
        const image = new Image();
        image.onload = () => resolve(convertToPng({
          src: image,
          width: image.width,
          height: image.height
        }));
        image.onerror = () => {
          reject(`There was an error loading the data URI as an image on the following SVG\n${window.atob(uri.slice(26))}Open the following link to see browser's diagnosis\n${uri}`);
        }
        image.src = uri;
      })
    });
  };

  out$.download = (name, uri) => {
    if (navigator.msSaveOrOpenBlob) navigator.msSaveOrOpenBlob(uriToBlob(uri), name);
    else {
      const saveLink = document.createElement('a');
      if ('download' in saveLink) {
        saveLink.download = name;
        saveLink.style.display = 'none';
        document.body.appendChild(saveLink);
        try {
          const blob = uriToBlob(uri);
          const url = URL.createObjectURL(blob);
          saveLink.href = url;
          saveLink.onclick = () => requestAnimationFrame(() => URL.revokeObjectURL(url));
        } catch (e) {
          console.error(e);
          console.warn('Error while getting object URL. Falling back to string URL.');
          saveLink.href = uri;
        }
        saveLink.click();
        document.body.removeChild(saveLink);
      } else {
        window.open(uri, '_temp', 'menubar=no,toolbar=no,status=no');
      }
    }
  };

  out$.saveSvg = (el, name, options) => {
    return requireDomNodePromise(el)
      .then(el => out$.svgAsDataUri(el, options || {}))
      .then(uri => out$.download(name, uri));
  };

  out$.saveSvgAsPng = (el, name, options) => {
    return requireDomNodePromise(el)
      .then(el => out$.svgAsPngUri(el, options || {}))
      .then(uri => out$.download(name, uri));
  };
})();
<commit_msg>Change download fallback for iOS.<commit_after>(function() {
  const out$ = typeof exports != 'undefined' && exports || typeof define != 'undefined' && {} || this || window;
  if (typeof define !== 'undefined') define('save-svg-as-png', [], () => out$);
  out$.default = out$;

  const xmlNs = 'http://www.w3.org/2000/xmlns/';
  const xhtmlNs = 'http://www.w3.org/1999/xhtml';
  const svgNs = 'http://www.w3.org/2000/svg';
  const doctype = '<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [<!ENTITY nbsp " ">]>';
  const urlRegex = /url\(["']?(.+?)["']?\)/;
  const fontFormats = {
    woff2: 'font/woff2',
    woff: 'font/woff',
    otf: 'application/x-font-opentype',
    ttf: 'application/x-font-ttf',
    eot: 'application/vnd.ms-fontobject',
    sfnt: 'application/font-sfnt',
    svg: 'image/svg+xml'
  };

  const isElement = obj => obj instanceof HTMLElement || obj instanceof SVGElement;
  const requireDomNode = el => {
    if (!isElement(el)) throw new Error(`an HTMLElement or SVGElement is required; got ${el}`);
  };
  const requireDomNodePromise = el =>
    new Promise((resolve, reject) => {
      if (isElement(el)) resolve(el)
      else reject(new Error(`an HTMLElement or SVGElement is required; got ${el}`));
    })
  const isExternal = url => url && url.lastIndexOf('http',0) === 0 && url.lastIndexOf(window.location.host) === -1;

  const getFontMimeTypeFromUrl = fontUrl => {
    const formats = Object.keys(fontFormats)
      .filter(extension => fontUrl.indexOf(`.${extension}`) > 0)
      .map(extension => fontFormats[extension]);
    if (formats) return formats[0];
    console.error(`Unknown font format for ${fontUrl}. Fonts may not be working correctly.`);
    return 'application/octet-stream';
  };

  const arrayBufferToBase64 = buffer => {
    let binary = '';
    const bytes = new Uint8Array(buffer);
    for (let i = 0; i < bytes.byteLength; i++) binary += String.fromCharCode(bytes[i]);
    return window.btoa(binary);
  }

  const getDimension = (el, clone, dim) => {
    const v =
      (el.viewBox && el.viewBox.baseVal && el.viewBox.baseVal[dim]) ||
      (clone.getAttribute(dim) !== null && !clone.getAttribute(dim).match(/%$/) && parseInt(clone.getAttribute(dim))) ||
      el.getBoundingClientRect()[dim] ||
      parseInt(clone.style[dim]) ||
      parseInt(window.getComputedStyle(el).getPropertyValue(dim));
    return typeof v === 'undefined' || v === null || isNaN(parseFloat(v)) ? 0 : v;
  };

  const getDimensions = (el, clone, width, height) => {
    if (el.tagName === 'svg') return {
      width: width || getDimension(el, clone, 'width'),
      height: height || getDimension(el, clone, 'height')
    };
    else if (el.getBBox) {
      const {x, y, width, height} = el.getBBox();
      return {
        width: x + width,
        height: y + height
      };
    }
  };

  const reEncode = data =>
    decodeURIComponent(
      encodeURIComponent(data)
        .replace(/%([0-9A-F]{2})/g, (match, p1) => {
          const c = String.fromCharCode(`0x${p1}`);
          return c === '%' ? '%25' : c;
        })
    );

  const uriToBlob = uri => {
    const byteString = window.atob(uri.split(',')[1]);
    const mimeString = uri.split(',')[0].split(':')[1].split(';')[0]
    const buffer = new ArrayBuffer(byteString.length);
    const intArray = new Uint8Array(buffer);
    for (let i = 0; i < byteString.length; i++) {
      intArray[i] = byteString.charCodeAt(i);
    }
    return new Blob([buffer], {type: mimeString});
  };

  const query = (el, selector) => {
    if (!selector) return;
    try {
      return el.querySelector(selector) || el.parentNode && el.parentNode.querySelector(selector);
    } catch(err) {
      console.warn(`Invalid CSS selector "${selector}"`, err);
    }
  };

  const detectCssFont = (rule, href) => {
    // Match CSS font-face rules to external links.
    // @font-face {
    //   src: local('Abel'), url(https://fonts.gstatic.com/s/abel/v6/UzN-iejR1VoXU2Oc-7LsbvesZW2xOQ-xsNqO47m55DA.woff2);
    // }
    const match = rule.cssText.match(urlRegex);
    const url = (match && match[1]) || '';
    if (!url || url.match(/^data:/) || url === 'about:blank') return;
    const fullUrl =
      url.startsWith('../') ? `${href}/../${url}`
      : url.startsWith('./') ? `${href}/.${url}`
      : url;
    return {
      text: rule.cssText,
      format: getFontMimeTypeFromUrl(fullUrl),
      url: fullUrl
    };
  };

  const inlineImages = el => Promise.all(
    Array.from(el.querySelectorAll('image')).map(image => {
      let href = image.getAttributeNS('http://www.w3.org/1999/xlink', 'href') || image.getAttribute('href');
      if (!href) return Promise.resolve(null);
      if (isExternal(href)) {
        href += (href.indexOf('?') === -1 ? '?' : '&') + 't=' + new Date().valueOf();
      }
      return new Promise((resolve, reject) => {
        const canvas = document.createElement('canvas');
        const img = new Image();
        img.crossOrigin = 'anonymous';
        img.src = href;
        img.onerror = () => reject(new Error(`Could not load ${href}`));
        img.onload = () => {
          canvas.width = img.width;
          canvas.height = img.height;
          canvas.getContext('2d').drawImage(img, 0, 0);
          image.setAttributeNS('http://www.w3.org/1999/xlink', 'href', canvas.toDataURL('image/png'));
          resolve(true);
        };
      });
    })
  );

  const cachedFonts = {};
  const inlineFonts = fonts => Promise.all(
    fonts.map(font =>
      new Promise((resolve, reject) => {
        if (cachedFonts[font.url]) return resolve(cachedFonts[font.url]);

        const req = new XMLHttpRequest();
        req.addEventListener('load', () => {
          // TODO: it may also be worth it to wait until fonts are fully loaded before
          // attempting to rasterize them. (e.g. use https://developer.mozilla.org/en-US/docs/Web/API/FontFaceSet)
          const fontInBase64 = arrayBufferToBase64(req.response);
          const fontUri = font.text.replace(urlRegex, `url("data:${font.format};base64,${fontInBase64}")`)+'\n';
          cachedFonts[font.url] = fontUri;
          resolve(fontUri);
        });
        req.addEventListener('error', e => {
          console.warn(`Failed to load font from: ${font.url}`, e);
          cachedFonts[font.url] = null;
          resolve(null);
        });
        req.addEventListener('abort', e => {
          console.warn(`Aborted loading font from: ${font.url}`, e);
          resolve(null);
        });
        req.open('GET', font.url);
        req.responseType = 'arraybuffer';
        req.send();
      })
    )
  ).then(fontCss => fontCss.filter(x => x).join(''));

  let cachedRules = null;
  const styleSheetRules = () => {
    if (cachedRules) return cachedRules;
    return cachedRules = Array.from(document.styleSheets).map(sheet => {
      try {
        return {rules: sheet.cssRules, href: sheet.href};
      } catch (e) {
        console.warn(`Stylesheet could not be loaded: ${sheet.href}`, e);
        return {};
      }
    });
  };

  const inlineCss = (el, options) => {
    const {
      selectorRemap,
      modifyStyle,
      modifyCss,
      fonts
    } = options || {};
    const generateCss = modifyCss || ((selector, properties) => {
      const sel = selectorRemap ? selectorRemap(selector) : selector;
      const props = modifyStyle ? modifyStyle(properties) : properties;
      return `${sel}{${props}}\n`;
    });
    const css = [];
    const detectFonts = typeof fonts === 'undefined';
    const fontList = fonts || [];
    styleSheetRules().forEach(({rules, href}) => {
      if (!rules) return;
      Array.from(rules).forEach(rule => {
        if (typeof rule.style != 'undefined') {
          if (query(el, rule.selectorText)) css.push(generateCss(rule.selectorText, rule.style.cssText));
          else if (detectFonts && rule.cssText.match(/^@font-face/)) {
            const font = detectCssFont(rule, href);
            if (font) fontList.push(font);
          } else css.push(rule.cssText);
        }
      });
    });

    return inlineFonts(fontList).then(fontCss => css.join('\n') + fontCss);
  };

  out$.prepareSvg = (el, options, done) => {
    requireDomNode(el);
    const {
      left = 0,
      top = 0,
      width: w,
      height: h,
      scale = 1,
      responsive = false,
    } = options || {};

    return inlineImages(el).then(() => {
      let clone = el.cloneNode(true);
      clone.style.backgroundColor = (options || {}).backgroundColor || el.style.backgroundColor;
      const {width, height} = getDimensions(el, clone, w, h);

      if (el.tagName !== 'svg') {
        if (el.getBBox) {
          if (clone.getAttribute('transform') != null) {
            clone.setAttribute('transform', clone.getAttribute('transform').replace(/translate\(.*?\)/, ''));
          }
          const svg = document.createElementNS('http://www.w3.org/2000/svg','svg');
          svg.appendChild(clone);
          clone = svg;
        } else {
          console.error('Attempted to render non-SVG element', el);
          return;
        }
      }

      clone.setAttribute('version', '1.1');
      clone.setAttribute('viewBox', [left, top, width, height].join(' '));
      if (!clone.getAttribute('xmlns')) clone.setAttributeNS(xmlNs, 'xmlns', svgNs);
      if (!clone.getAttribute('xmlns:xlink')) clone.setAttributeNS(xmlNs, 'xmlns:xlink', 'http://www.w3.org/1999/xlink');

      if (responsive) {
        clone.removeAttribute('width');
        clone.removeAttribute('height');
        clone.setAttribute('preserveAspectRatio', 'xMinYMin meet');
      } else {
        clone.setAttribute('width', width * scale);
        clone.setAttribute('height', height * scale);
      }

      Array.from(clone.querySelectorAll('foreignObject > *')).forEach(foreignObject => {
        foreignObject.setAttributeNS(xmlNs, 'xmlns', foreignObject.tagName === 'svg' ? svgNs : xhtmlNs);
      });

      return inlineCss(el, options).then(css => {
        const style = document.createElement('style');
        style.setAttribute('type', 'text/css');
        style.innerHTML = `<![CDATA[\n${css}\n]]>`;

        const defs = document.createElement('defs');
        defs.appendChild(style);
        clone.insertBefore(defs, clone.firstChild);

        const outer = document.createElement('div');
        outer.appendChild(clone);
        const src = outer.innerHTML.replace(/NS\d+:href/gi, 'xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href');

        if (typeof done === 'function') done(src, width, height);
        else return {src, width, height};
      });
    });
  };

  out$.svgAsDataUri = (el, options, done) => {
    requireDomNode(el);
    return out$.prepareSvg(el, options)
      .then(({src, width, height}) => {
          const svgXml = `data:image/svg+xml;base64,${window.btoa(reEncode(doctype+src))}`;
          if (typeof done === 'function') {
              done(svgXml, width, height);
          }
          return svgXml;
      });
  };

  out$.svgAsPngUri = (el, options, done) => {
    requireDomNode(el);
    const {
      encoderType = 'image/png',
      encoderOptions = 0.8,
      canvg
    } = options || {};

    const convertToPng = ({src, width, height}) => {
      const canvas = document.createElement('canvas');
      const context = canvas.getContext('2d');
      const pixelRatio = window.devicePixelRatio || 1;

      canvas.width = width * pixelRatio;
      canvas.height = height * pixelRatio;
      canvas.style.width = `${canvas.width}px`;
      canvas.style.height = `${canvas.height}px`;
      context.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);

      if (canvg) canvg(canvas, src);
      else context.drawImage(src, 0, 0);

      let png;
      try {
        png = canvas.toDataURL(encoderType, encoderOptions);
      } catch (e) {
        if ((typeof SecurityError !== 'undefined' && e instanceof SecurityError) || e.name === 'SecurityError') {
          console.error('Rendered SVG images cannot be downloaded in this browser.');
          return;
        } else throw e;
      }
      if (typeof done === 'function') done(png, canvas.width, canvas.height);
      return Promise.resolve(png);
    }

    if (canvg) return out$.prepareSvg(el, options).then(convertToPng);
    else return out$.svgAsDataUri(el, options).then(uri => {
      return new Promise((resolve, reject) => {
        const image = new Image();
        image.onload = () => resolve(convertToPng({
          src: image,
          width: image.width,
          height: image.height
        }));
        image.onerror = () => {
          reject(`There was an error loading the data URI as an image on the following SVG\n${window.atob(uri.slice(26))}Open the following link to see browser's diagnosis\n${uri}`);
        }
        image.src = uri;
      })
    });
  };

  out$.download = (name, uri) => {
    if (navigator.msSaveOrOpenBlob) navigator.msSaveOrOpenBlob(uriToBlob(uri), name);
    else {
      const saveLink = document.createElement('a');
      if ('download' in saveLink) {
        saveLink.download = name;
        saveLink.style.display = 'none';
        document.body.appendChild(saveLink);
        try {
          const blob = uriToBlob(uri);
          const url = URL.createObjectURL(blob);
          saveLink.href = url;
          saveLink.onclick = () => requestAnimationFrame(() => URL.revokeObjectURL(url));
        } catch (e) {
          console.error(e);
          console.warn('Error while getting object URL. Falling back to string URL.');
          saveLink.href = uri;
        }
        saveLink.click();
        document.body.removeChild(saveLink);
      } else {
        const popup = window.open();
        popup.document.title = name;
        popup.location.replace(uri);
      }
    }
  };

  out$.saveSvg = (el, name, options) => {
    return requireDomNodePromise(el)
      .then(el => out$.svgAsDataUri(el, options || {}))
      .then(uri => out$.download(name, uri));
  };

  out$.saveSvgAsPng = (el, name, options) => {
    return requireDomNodePromise(el)
      .then(el => out$.svgAsPngUri(el, options || {}))
      .then(uri => out$.download(name, uri));
  };
})();
<|endoftext|>


<commit_before>/* global $:true */

require('../../styles/container/container.css');
require('../../styles/input-autocomplete/input-autocomplete');
require('../../styles/add-quest/add-quest');
require('../../styles/tags/tags');

$('#title, #city, #description').on('blur', function () {
    if ($(this).val() === '') {
        $(this).addClass('input_error');
        $(this).attr('placeholder', 'Please enter ' + $(this).attr('id'));
    } else {
        $(this).removeClass('input_error');
    }

    var empty = false;

    $('input:required, textarea:required').each(function (idx, el) {
        var value = $(el).val();
        if (/^\s*$/.test(value)) {
            empty = true;
        }
    });

    if (empty) {
        $('button[role="createQuest"], button[role="editQuest"]').addClass('btn_disabled');
    } else {
        $('button[role="createQuest"], button[role="editQuest"]').removeClass('btn_disabled');
    }
});

$('button[role="editQuest"]').on('click', function () {
    if ($(this).hasClass('btn_disabled')) {
        return;
    }

    var tags = [];

    $('.tag').each(function (idx, el) {
        var tag = $(el).text().trim();
        tags.push(tag);
    });

    var msg = {};
    msg.title = $('#title').val();
    msg.description = $('#description').val();
    msg.city = $('#city').val();
    msg.slug = $('#slug').val();
    msg.tags = tags;

    $.ajax({
        type: 'POST',
        url: '/api/quests/' + msg.slug + '/edit', // SLUG
        data: msg,
        success: function ({data}) {
            $('.container').html('Квест успешно изменен =)');

            setTimeout(function () {
                window.location.href = '/quests/' + data.slug;
            }, 2000);
        },
        error: function () {
            $('.container').html('При изменении квеста произошла ошибка, попробуйте еще раз =(');

            setTimeout(function () {
                window.location.reload();
            }, 2000);
        }
    });
});

$('button[role="createQuest"]').on('click', function () {
    if ($(this).hasClass('btn_disabled')) {
        return;
    }

    var stagesForms = $('.quests-content form');
    var tags = [];

    $('.tag').each(function (idx, el) {
        var tag = $(el).text().trim();
        tags.push(tag);
    });

    sendForm();

    function sendForm() {
        var msg = {};
        msg.title = $('#title').val();
        msg.description = $('#description').val();
        msg.city = $('#city').val();
        msg.tags = tags;

        $.ajax({
            type: 'POST',
            url: '/api/quests',
            data: msg,
            success: function ({data}) {
                $(stagesForms).each(function (idx, el) {
                    sendStageForm(el, data.slug);
                });

                $('.container').html('Квест успешно создан =)');

                setTimeout(function () {
                    window.location.href = '/quests/' + data.slug;
                }, 2000);
            },
            error: function () {
                $('.container').html('При создании квеста произошла ошибка, попробуйте еще раз =(');

                setTimeout(function () {
                    window.location.reload();
                }, 2000);
            }
        });
    }

    function sendStageForm(form, slug) {
        var msg = $(form).serialize();
        $.ajax({
            type: 'POST',
            url: '/api/quests/' + slug + '/stages',
            data: msg,
            success: function () {

            },
            error: function () {

            }
        });
    }
});

$('.quests-content').on('keyup', '.title', function () {
    var panel = $(this).closest('.tab-content__panel');
    var id = $(panel).attr('id');

    var tab = $('.tabs').find('a[href="#' + id + '"]');
    var val = $(this).val();
    if (/^\s*$/.test(val)) {
        $(tab).html('Без названия');
    } else {
        $(tab).html(val);
    }
});

$('.quests-content').on('click', '.add-quest_delete', function () {
    var tabContent = $(this).parent();

    var tabId = $(tabContent).attr('id');
    var tab = $('.add-quest__tabs').find('a[href="#' + tabId + '"]').parent();

    $(tab).remove();
    $(tabContent).remove();

    var newActiveTab = $('.add-quest__tabs').children();
    if (newActiveTab.length > 0) {
        $(newActiveTab[0]).find('.tabs__link').addClass('active');
    }

    var newActiveTabContent = $('.quests-content').children();
    if (newActiveTabContent.length > 0) {
        $(newActiveTabContent[0]).addClass('active');
    }
});
<commit_msg>поправил замечания<commit_after>/* global $:true */

require('../../styles/container/container.css');
require('../../styles/input-autocomplete/input-autocomplete');
require('../../styles/add-quest/add-quest');
require('../../styles/tags/tags');

$('#title, #city, #description').on('blur', function () {
    if ($(this).val() === '') {
        $(this).addClass('input_error');
        $(this).attr('placeholder', 'Please enter ' + $(this).attr('id'));
    } else {
        $(this).removeClass('input_error');
    }

    var empty = false;

    $('input:required, textarea:required').each(function (idx, el) {
        var value = $(el).val();
        if (/^\s*$/.test(value)) {
            empty = true;
        }
    });

    if (empty) {
        $('button[role="createQuest"], button[role="editQuest"]').addClass('btn_disabled');
    } else {
        $('button[role="createQuest"], button[role="editQuest"]').removeClass('btn_disabled');
    }
});

$('button[role="editQuest"]').on('click', function () {
    if ($(this).hasClass('btn_disabled')) {
        return;
    }

    var tags = [];

    $('.tag').each(function (idx, el) {
        var tag = $(el).text().trim();
        tags.push(tag);
    });

    var msg = {};
    msg.title = $('#title').val();
    msg.description = $('#description').val();
    msg.city = $('#city').val();
    msg.slug = $('#slug').val();
    msg.tags = tags;

    $.ajax({
        type: 'POST',
        url: '/api/quests/' + msg.slug + '/edit', // SLUG
        data: msg,
        success: function ({data}) {
            $('.container').html('Квест успешно изменен');

            setTimeout(function () {
                window.location.href = '/quests/' + data.slug;
            }, 2000);
        },
        error: function () {
            $('.container').html('При изменении квеста произошла ошибка, попробуйте еще раз');

            setTimeout(function () {
                window.location.reload();
            }, 2000);
        }
    });
});

$('button[role="createQuest"]').on('click', function () {
    if ($(this).hasClass('btn_disabled')) {
        return;
    }

    var stagesForms = $('.quests-content form');
    var tags = [];

    $('.tag').each(function (idx, el) {
        var tag = $(el).text().trim();
        tags.push(tag);
    });

    sendForm();

    function sendForm() {
        var msg = {};
        msg.title = $('#title').val();
        msg.description = $('#description').val();
        msg.city = $('#city').val();
        msg.tags = tags;

        $.ajax({
            type: 'POST',
            url: '/api/quests',
            data: msg,
            success: function ({data}) {
                $(stagesForms).each(function (idx, el) {
                    sendStageForm(el, data.slug);
                });

                $('.container').html('Квест успешно создан');

                setTimeout(function () {
                    window.location.href = '/quests/' + data.slug;
                }, 2000);
            },
            error: function () {
                $('.container').html('При создании квеста произошла ошибка, попробуйте еще раз');

                setTimeout(function () {
                    window.location.reload();
                }, 2000);
            }
        });
    }

    function sendStageForm(form, slug) {
        var msg = $(form).serialize();
        $.ajax({
            type: 'POST',
            url: '/api/quests/' + slug + '/stages',
            data: msg,
            success: function () {

            },
            error: function () {

            }
        });
    }
});

$('.quests-content').on('keyup', '.title', function () {
    var panel = $(this).closest('.tab-content__panel');
    var id = $(panel).attr('id');

    var tab = $('.tabs').find('a[href="#' + id + '"]');
    var val = $(this).val();
    if (/^\s*$/.test(val)) {
        $(tab).html('Без названия');
    } else {
        $(tab).html(val);
    }
});

$('.quests-content').on('click', '.add-quest_delete', function () {
    var tabContent = $(this).parent();

    var tabId = $(tabContent).attr('id');
    var tab = $('.add-quest__tabs').find('a[href="#' + tabId + '"]').parent();

    $(tab).remove();
    $(tabContent).remove();

    var newActiveTab = $('.add-quest__tabs').children();
    if (newActiveTab.length > 0) {
        $(newActiveTab[0]).find('.tabs__link').addClass('active');
    }

    var newActiveTabContent = $('.quests-content').children();
    if (newActiveTabContent.length > 0) {
        $(newActiveTabContent[0]).addClass('active');
    }
});
<|endoftext|>


<commit_before>/**
 * Base for all chart types. The methods in Chartist.Base are inherited to all chart types.
 *
 * @module Chartist.Base
 */
/* global Chartist */
(function(window, document, Chartist) {
  'use strict';

  // TODO: Currently we need to re-draw the chart on window resize. This is usually very bad and will affect performance.
  // This is done because we can't work with relative coordinates when drawing the chart because SVG Path does not
  // work with relative positions yet. We need to check if we can do a viewBox hack to switch to percentage.
  // See http://mozilla.6506.n7.nabble.com/Specyfing-paths-with-percentages-unit-td247474.html
  // Update: can be done using the above method tested here: http://codepen.io/gionkunz/pen/KDvLj
  // The problem is with the label offsets that can't be converted into percentage and affecting the chart container
  /**
   * Updates the chart which currently does a full reconstruction of the SVG DOM
   *
   * @param {Object} [data] Optional data you'd like to set for the chart before it will update. If not specified the update method will use the data that is already configured with the chart.
   * @param {Object} [options] Optional options you'd like to add to the previous options for the chart before it will update. If not specified the update method will use the options that have been already configured with the chart.
   * @param {Boolean} [override] If set to true, the passed options will be used to extend the options that have been configured already. Otherwise the chart default options will be used as the base
   * @memberof Chartist.Base
   */
  function update(data, options, override) {
    if(data) {
      this.data = data;
      // Event for data transformation that allows to manipulate the data before it gets rendered in the charts
      this.eventEmitter.emit('data', {
        type: 'update',
        data: this.data
      });
    }

    if(options) {
      this.options = Chartist.extend({}, override ? this.options : this.defaultOptions, options);

      // If chartist was not initialized yet, we just set the options and leave the rest to the initialization
      // Otherwise we re-create the optionsProvider at this point
      if(!this.initializeTimeoutId) {
        this.optionsProvider.removeMediaQueryListeners();
        this.optionsProvider = Chartist.optionsProvider(this.options, this.responsiveOptions, this.eventEmitter);
      }
    }

    // Only re-created the chart if it has been initialized yet
    if(!this.initializeTimeoutId) {
      this.createChart(this.optionsProvider.getCurrentOptions());
    }

    // Return a reference to the chart object to chain up calls
    return this;
  }

  /**
   * This method can be called on the API object of each chart and will un-register all event listeners that were added to other components. This currently includes a window.resize listener as well as media query listeners if any responsive options have been provided. Use this function if you need to destroy and recreate Chartist charts dynamically.
   *
   * @memberof Chartist.Base
   */
  function detach() {
    // Only detach if initialization already occurred on this chart. If this chart still hasn't initialized (therefore
    // the initializationTimeoutId is still a valid timeout reference, we will clear the timeout
    if(!this.initializeTimeoutId) {
      window.removeEventListener('resize', this.resizeListener);
      this.optionsProvider.removeMediaQueryListeners();
    } else {
      window.clearTimeout(this.initializeTimeoutId);
    }

    return this;
  }

  /**
   * Use this function to register event handlers. The handler callbacks are synchronous and will run in the main thread rather than the event loop.
   *
   * @memberof Chartist.Base
   * @param {String} event Name of the event. Check the examples for supported events.
   * @param {Function} handler The handler function that will be called when an event with the given name was emitted. This function will receive a data argument which contains event data. See the example for more details.
   */
  function on(event, handler) {
    this.eventEmitter.addEventHandler(event, handler);
    return this;
  }

  /**
   * Use this function to un-register event handlers. If the handler function parameter is omitted all handlers for the given event will be un-registered.
   *
   * @memberof Chartist.Base
   * @param {String} event Name of the event for which a handler should be removed
   * @param {Function} [handler] The handler function that that was previously used to register a new event handler. This handler will be removed from the event handler list. If this parameter is omitted then all event handlers for the given event are removed from the list.
   */
  function off(event, handler) {
    this.eventEmitter.removeEventHandler(event, handler);
    return this;
  }

  function initialize() {
    // Add window resize listener that re-creates the chart
    window.addEventListener('resize', this.resizeListener);

    // Obtain current options based on matching media queries (if responsive options are given)
    // This will also register a listener that is re-creating the chart based on media changes
    this.optionsProvider = Chartist.optionsProvider(this.options, this.responsiveOptions, this.eventEmitter);
    // Register options change listener that will trigger a chart update
    this.eventEmitter.addEventHandler('optionsChanged', function() {
      this.update();
    }.bind(this));

    // Before the first chart creation we need to register us with all plugins that are configured
    // Initialize all relevant plugins with our chart object and the plugin options specified in the config
    if(this.options.plugins) {
      this.options.plugins.forEach(function(plugin) {
        if(plugin instanceof Array) {
          plugin[0](this, plugin[1]);
        } else {
          plugin(this);
        }
      }.bind(this));
    }

    // Event for data transformation that allows to manipulate the data before it gets rendered in the charts
    this.eventEmitter.emit('data', {
      type: 'initial',
      data: this.data
    });

    // Create the first chart
    this.createChart(this.optionsProvider.getCurrentOptions());

    // As chart is initialized from the event loop now we can reset our timeout reference
    // This is important if the chart gets initialized on the same element twice
    this.initializeTimeoutId = undefined;
  }

  /**
   * Constructor of chart base class.
   *
   * @param query
   * @param data
   * @param defaultOptions
   * @param options
   * @param responsiveOptions
   * @constructor
   */
  function Base(query, data, defaultOptions, options, responsiveOptions) {
    this.container = Chartist.querySelector(query);
    this.data = data;
    this.defaultOptions = defaultOptions;
    this.options = options;
    this.responsiveOptions = responsiveOptions;
    this.eventEmitter = Chartist.EventEmitter();
    this.supportsForeignObject = Chartist.Svg.isSupported('Extensibility');
    this.supportsAnimations = Chartist.Svg.isSupported('AnimationEventsAttribute');
    this.resizeListener = function resizeListener(){
      this.update();
    }.bind(this);

    if(this.container) {
      // If chartist was already initialized in this container we are detaching all event listeners first
      if(this.container.__chartist__) {
        if(this.container.__chartist__.initializeTimeoutId) {
          // If the initializeTimeoutId is still set we can safely assume that the initialization function has not
          // been called yet from the event loop. Therefore we should cancel the timeout and don't need to detach
          window.clearTimeout(this.container.__chartist__.initializeTimeoutId);
        } else {
          // The timeout reference has already been reset which means we need to detach the old chart first
          this.container.__chartist__.detach();
        }
      }

      this.container.__chartist__ = this;
    }

    // Using event loop for first draw to make it possible to register event listeners in the same call stack where
    // the chart was created.
    this.initializeTimeoutId = setTimeout(initialize.bind(this), 0);
  }

  // Creating the chart base class
  Chartist.Base = Chartist.Class.extend({
    constructor: Base,
    optionsProvider: undefined,
    container: undefined,
    svg: undefined,
    eventEmitter: undefined,
    createChart: function() {
      throw new Error('Base chart type can\'t be instantiated!');
    },
    update: update,
    detach: detach,
    on: on,
    off: off,
    version: Chartist.version,
    supportsForeignObject: false
  });

}(window, document, Chartist));
<commit_msg>Refactored initialization code, cleaned redundant code<commit_after>/**
 * Base for all chart types. The methods in Chartist.Base are inherited to all chart types.
 *
 * @module Chartist.Base
 */
/* global Chartist */
(function(window, document, Chartist) {
  'use strict';

  // TODO: Currently we need to re-draw the chart on window resize. This is usually very bad and will affect performance.
  // This is done because we can't work with relative coordinates when drawing the chart because SVG Path does not
  // work with relative positions yet. We need to check if we can do a viewBox hack to switch to percentage.
  // See http://mozilla.6506.n7.nabble.com/Specyfing-paths-with-percentages-unit-td247474.html
  // Update: can be done using the above method tested here: http://codepen.io/gionkunz/pen/KDvLj
  // The problem is with the label offsets that can't be converted into percentage and affecting the chart container
  /**
   * Updates the chart which currently does a full reconstruction of the SVG DOM
   *
   * @param {Object} [data] Optional data you'd like to set for the chart before it will update. If not specified the update method will use the data that is already configured with the chart.
   * @param {Object} [options] Optional options you'd like to add to the previous options for the chart before it will update. If not specified the update method will use the options that have been already configured with the chart.
   * @param {Boolean} [override] If set to true, the passed options will be used to extend the options that have been configured already. Otherwise the chart default options will be used as the base
   * @memberof Chartist.Base
   */
  function update(data, options, override) {
    if(data) {
      this.data = data;
      // Event for data transformation that allows to manipulate the data before it gets rendered in the charts
      this.eventEmitter.emit('data', {
        type: 'update',
        data: this.data
      });
    }

    if(options) {
      this.options = Chartist.extend({}, override ? this.options : this.defaultOptions, options);

      // If chartist was not initialized yet, we just set the options and leave the rest to the initialization
      // Otherwise we re-create the optionsProvider at this point
      if(!this.initializeTimeoutId) {
        this.optionsProvider.removeMediaQueryListeners();
        this.optionsProvider = Chartist.optionsProvider(this.options, this.responsiveOptions, this.eventEmitter);
      }
    }

    // Only re-created the chart if it has been initialized yet
    if(!this.initializeTimeoutId) {
      this.createChart(this.optionsProvider.getCurrentOptions());
    }

    // Return a reference to the chart object to chain up calls
    return this;
  }

  /**
   * This method can be called on the API object of each chart and will un-register all event listeners that were added to other components. This currently includes a window.resize listener as well as media query listeners if any responsive options have been provided. Use this function if you need to destroy and recreate Chartist charts dynamically.
   *
   * @memberof Chartist.Base
   */
  function detach() {
    // Only detach if initialization already occurred on this chart. If this chart still hasn't initialized (therefore
    // the initializationTimeoutId is still a valid timeout reference, we will clear the timeout
    if(!this.initializeTimeoutId) {
      window.removeEventListener('resize', this.resizeListener);
      this.optionsProvider.removeMediaQueryListeners();
    } else {
      window.clearTimeout(this.initializeTimeoutId);
    }

    return this;
  }

  /**
   * Use this function to register event handlers. The handler callbacks are synchronous and will run in the main thread rather than the event loop.
   *
   * @memberof Chartist.Base
   * @param {String} event Name of the event. Check the examples for supported events.
   * @param {Function} handler The handler function that will be called when an event with the given name was emitted. This function will receive a data argument which contains event data. See the example for more details.
   */
  function on(event, handler) {
    this.eventEmitter.addEventHandler(event, handler);
    return this;
  }

  /**
   * Use this function to un-register event handlers. If the handler function parameter is omitted all handlers for the given event will be un-registered.
   *
   * @memberof Chartist.Base
   * @param {String} event Name of the event for which a handler should be removed
   * @param {Function} [handler] The handler function that that was previously used to register a new event handler. This handler will be removed from the event handler list. If this parameter is omitted then all event handlers for the given event are removed from the list.
   */
  function off(event, handler) {
    this.eventEmitter.removeEventHandler(event, handler);
    return this;
  }

  function initialize() {
    // Add window resize listener that re-creates the chart
    window.addEventListener('resize', this.resizeListener);

    // Obtain current options based on matching media queries (if responsive options are given)
    // This will also register a listener that is re-creating the chart based on media changes
    this.optionsProvider = Chartist.optionsProvider(this.options, this.responsiveOptions, this.eventEmitter);
    // Register options change listener that will trigger a chart update
    this.eventEmitter.addEventHandler('optionsChanged', function() {
      this.update();
    }.bind(this));

    // Before the first chart creation we need to register us with all plugins that are configured
    // Initialize all relevant plugins with our chart object and the plugin options specified in the config
    if(this.options.plugins) {
      this.options.plugins.forEach(function(plugin) {
        if(plugin instanceof Array) {
          plugin[0](this, plugin[1]);
        } else {
          plugin(this);
        }
      }.bind(this));
    }

    // Event for data transformation that allows to manipulate the data before it gets rendered in the charts
    this.eventEmitter.emit('data', {
      type: 'initial',
      data: this.data
    });

    // Create the first chart
    this.createChart(this.optionsProvider.getCurrentOptions());

    // As chart is initialized from the event loop now we can reset our timeout reference
    // This is important if the chart gets initialized on the same element twice
    this.initializeTimeoutId = undefined;
  }

  /**
   * Constructor of chart base class.
   *
   * @param query
   * @param data
   * @param defaultOptions
   * @param options
   * @param responsiveOptions
   * @constructor
   */
  function Base(query, data, defaultOptions, options, responsiveOptions) {
    this.container = Chartist.querySelector(query);
    this.data = data;
    this.defaultOptions = defaultOptions;
    this.options = options;
    this.responsiveOptions = responsiveOptions;
    this.eventEmitter = Chartist.EventEmitter();
    this.supportsForeignObject = Chartist.Svg.isSupported('Extensibility');
    this.supportsAnimations = Chartist.Svg.isSupported('AnimationEventsAttribute');
    this.resizeListener = function resizeListener(){
      this.update();
    }.bind(this);

    if(this.container) {
      // If chartist was already initialized in this container we are detaching all event listeners first
      if(this.container.__chartist__) {
        this.container.__chartist__.detach();
      }

      this.container.__chartist__ = this;
    }

    // Using event loop for first draw to make it possible to register event listeners in the same call stack where
    // the chart was created.
    this.initializeTimeoutId = setTimeout(initialize.bind(this), 0);
  }

  // Creating the chart base class
  Chartist.Base = Chartist.Class.extend({
    constructor: Base,
    optionsProvider: undefined,
    container: undefined,
    svg: undefined,
    eventEmitter: undefined,
    createChart: function() {
      throw new Error('Base chart type can\'t be instantiated!');
    },
    update: update,
    detach: detach,
    on: on,
    off: off,
    version: Chartist.version,
    supportsForeignObject: false
  });

}(window, document, Chartist));
<|endoftext|>


<commit_before>(function(tiy, $) {
    'use strict';

    // Public API methods
    tiy.loadModule = loadModule;

    // locals & constants
    let tiyoData = null;
    let navUI = null;
    let mainUI = null;
    let modulesLoaded = [];
    let dataLoaded = false;
    let uiLoaded = false;

    const MAIN_TEMPLATE = 'build/templates/main.html';
    const NAV_TEMPLATE = 'build/templates/nav.html';

    function main() {
        console.info('Initializing TIYO assistant');

        addFontAwesomeStyleSheet();

        collectData()
            .then(function getNavUI(data) {
                tiyoData = data;
                dataLoaded = true;
                console.log('Data gathered', data);
                return $.get(chrome.extension.getURL(NAV_TEMPLATE));
            })
            .then(function getMainUI(html) {
                navUI = $(html);
                $('.main .header:first').append(navUI);

                modulesLoaded.forEach(addNavIcon);
                navUI.on('click', 'li', showModuleUI);

                return $.get(chrome.extension.getURL(MAIN_TEMPLATE));
            })
            .then(function callModuleRenders(html) {
                mainUI = $(html);
                $('.breadcrumb').after(mainUI);
                uiLoaded = true;

                setupContentClose();

                console.log('UI base loaded, calling render methods', mainUI);
                modulesLoaded.forEach(function(mod) {
                    doRender(mod);
                });
            });
    }

    /**
     * Chrome extensions have issues with access files from css like fonts, so
     * we need to embed the styelsheet using the chromse extension URL, which
     * is dynamically generated. The remainder of the FA styles are in a static
     * stylesheet in /vendor
     */
    function addFontAwesomeStyleSheet() {
        $('head').append(
            $('<style>')
                .attr('type', 'text/css')
                .text(
                    `@font-face {
                        font-family: FontAwesome;
                        src: url('${chrome.extension.getURL('vendor/fontawesome-webfont.woff')}');
                    }`
                )
        );
    }

    function collectData() {
        let data = {
                group: null,
                students: []
            },
            group = $('.card-block dt:contains("Group")').next().find('a');

        if (!group.length) { return; }

        data.group = {
            title: group.text(),
            id: Number(group.attr('href').match(/\/([0-9]+)/)[1])
        };

        return $.get(group.attr('href')).then(function(html) {
            let students = $(html).find('#students tr td:first-child a');
            students.each(function() {
                let studentElem = $(this).find('.profile-placeholder-medium, img').remove().end();
                data.students.push({
                    id: Number(studentElem.attr('href').match(/\/([0-9]+)/)[1]),
                    name: studentElem.text()
                });
            });
            return data;
        });
    }

    function addNavIcon(mod) {
        if (!navUI) { return; }

        navUI.find('ul').append(
            `<li data-module='${mod.name}'><i class='fa ${mod.navIcon} fa-lg' aria-hidden='true'></i></li>`
        );
    }

    function showModuleUI(e) {
        e.preventDefault();
        var name = $(this).data('module');
        var mod = modulesLoaded.filter(function(m) { return m.name === name;});
        if (!mod.length) { return; }

        console.log('showing ', mod[0]);
        $('.tiyo-assistant-module').hide();
        $(`[data-module="${mod[0].name}"]`).show().trigger('showing');
        mainUI.slideDown(1000);
    }

    function setupContentClose() {
        $('.tiyo-assistant-close').click(function closeContent(e) {
            e.preventDefault();
            mainUI.slideUp(1000);
        });
    }

    function loadModule(api) {
        if (!api) { return; }
        if (!api.name) { api.name = Date.getTime(); }

        console.info('loading module', api);

        modulesLoaded.push(api);

        addNavIcon(api);
        doRender(api);
    }

    function doRender(mod) {
        if (uiLoaded && typeof(mod.render) === 'function') {
            var elem = createModuleWrapper(mod);
            mod.render(tiyoData, elem);
        }
    }

    function createModuleWrapper(mod) {
        var elem = $(`<article data-module='${mod.name}'>`).addClass('tiyo-assistant-module');
        $(mainUI).find('.tiyo-assistant-content').append(elem);
        return elem;
    }

    // Kick things off...
    main();
    // Then export our module API
    window.tiy = tiy;
    console.log(window.tiy);

})(window.tiy || {}, window.jQuery);
<commit_msg>Added path id and title to main data<commit_after>(function(tiy, $) {
    'use strict';

    // Public API methods
    tiy.loadModule = loadModule;

    // locals & constants
    let tiyoData = null;
    let navUI = null;
    let mainUI = null;
    let modulesLoaded = [];
    let dataLoaded = false;
    let uiLoaded = false;

    const MAIN_TEMPLATE = 'build/templates/main.html';
    const NAV_TEMPLATE = 'build/templates/nav.html';

    function main() {
        console.info('Initializing TIYO assistant');

        addFontAwesomeStyleSheet();

        collectData()
            .then(function getNavUI(data) {
                tiyoData = data;
                dataLoaded = true;
                console.log('Data gathered', data);
                return $.get(chrome.extension.getURL(NAV_TEMPLATE));
            })
            .then(function getMainUI(html) {
                navUI = $(html);
                $('.main .header:first').append(navUI);

                modulesLoaded.forEach(addNavIcon);
                navUI.on('click', 'li', showModuleUI);

                return $.get(chrome.extension.getURL(MAIN_TEMPLATE));
            })
            .then(function callModuleRenders(html) {
                mainUI = $(html);
                $('.breadcrumb').after(mainUI);
                uiLoaded = true;

                setupContentClose();

                console.log('UI base loaded, calling render methods', mainUI);
                modulesLoaded.forEach(function(mod) {
                    doRender(mod);
                });
            });
    }

    /**
     * Chrome extensions have issues with access files from css like fonts, so
     * we need to embed the styelsheet using the chromse extension URL, which
     * is dynamically generated. The remainder of the FA styles are in a static
     * stylesheet in /vendor
     */
    function addFontAwesomeStyleSheet() {
        $('head').append(
            $('<style>')
                .attr('type', 'text/css')
                .text(
                    `@font-face {
                        font-family: FontAwesome;
                        src: url('${chrome.extension.getURL('vendor/fontawesome-webfont.woff')}');
                    }`
                )
        );
    }

    function collectData() {
        let path = window.location.pathname.split(/\//),
            data = {
                path: null,
                group: null,
                students: []
            },
            group = $('.card-block dt:contains("Group")').next().find('a');

        if (path.length === 4 && path[2] === 'paths') {
            data.path = {
                id: Number(path[3]),
                title: $('.content .breadcrumb li:eq(1)').text()
            };
        }

        let p;
        if (group.length) {
            data.group = {
                title: group.text(),
                id: Number(group.attr('href').match(/\/([0-9]+)/)[1])
            };

            p = $.get(group.attr('href')).then(function(html) {
                let students = $(html).find('#students tr td:first-child a');
                students.each(function() {
                    let studentElem = $(this).find('.profile-placeholder-medium, img').remove().end();
                    data.students.push({
                        id: Number(studentElem.attr('href').match(/\/([0-9]+)/)[1]),
                        name: studentElem.text()
                    });
                });
                return data;
            });
        } else {
            p = Promise.resolve(data);
        }

        return p;
    }

    function addNavIcon(mod) {
        if (!navUI) { return; }

        navUI.find('ul').append(
            `<li data-module='${mod.name}'><i class='fa ${mod.navIcon} fa-lg' aria-hidden='true'></i></li>`
        );
    }

    function showModuleUI(e) {
        e.preventDefault();
        var name = $(this).data('module');
        var mod = modulesLoaded.filter(function(m) { return m.name === name;});
        if (!mod.length) { return; }

        console.log('showing ', mod[0]);
        $('.tiyo-assistant-module').hide();
        $(`[data-module="${mod[0].name}"]`).show().trigger('showing');
        mainUI.slideDown(1000);
    }

    function setupContentClose() {
        $('.tiyo-assistant-close').click(function closeContent(e) {
            e.preventDefault();
            mainUI.slideUp(1000);
        });
    }

    function loadModule(api) {
        if (!api) { return; }
        if (!api.name) { api.name = Date.getTime(); }

        console.info('loading module', api);

        modulesLoaded.push(api);

        addNavIcon(api);
        doRender(api);
    }

    function doRender(mod) {
        if (uiLoaded && typeof(mod.render) === 'function') {
            var elem = createModuleWrapper(mod);
            mod.render(tiyoData, elem);
        }
    }

    function createModuleWrapper(mod) {
        var elem = $(`<article data-module='${mod.name}'>`).addClass('tiyo-assistant-module');
        $(mainUI).find('.tiyo-assistant-content').append(elem);
        return elem;
    }

    // Kick things off...
    main();
    // Then export our module API
    window.tiy = tiy;
    console.log(window.tiy);

})(window.tiy || {}, window.jQuery);
<|endoftext|>


<commit_before>'use strict';

import { remote } from 'electron';
import path from 'path';
import i18n from '../i18n/index.js';

const { Tray, Menu } = remote;

const mainWindow = remote.getCurrentWindow();

const icons = {
    win32: {
        dir: 'windows'
    },
    linux: {
        dir: 'linux'
    },
    darwin: {
        dir: 'osx'
    }
};

const statusBullet = {
    online: '\u001B[32m•',
    away: '\u001B[33m•',
    busy: '\u001B[31m•',
    offline: '\u001B[30m•'
};

const messageCountColor = {
    white: '\u001B[37m',
    black: '\u001B[0m'
};

function getTrayImagePath (badge) {
    let iconFilename;
    if (badge.title === '•') {
        iconFilename = "icon-tray-dot";
    } else if (badge.count > 0) {
        if (badge.count > 9) {
            iconFilename = "icon-tray-9plus";
        } else {
            iconFilename = `icon-tray-${badge.count}`;
        }
    } else if (badge.showAlert) {
        iconFilename = "icon-tray-alert";
    } else {
        iconFilename = "icon-tray-Template";
    }

    if (process.platform === 'win32') {
        iconFilename += ".ico";
    } else {
        iconFilename += ".png";
    }

    return path.join(__dirname, 'images', icons[process.platform].dir, iconFilename);
}

function createAppTray () {
    const _tray = new Tray(getTrayImagePath({title:'',count:0,showAlert:false}));
    mainWindow.tray = _tray;

    const contextMenuShow = Menu.buildFromTemplate([{
        label: i18n.__('Show'),
        click () {
            mainWindow.show();
        }
    }, {
        label: i18n.__('Quit'),
        click () {
            remote.app.quit();
        }
    }]);

    const contextMenuHide = Menu.buildFromTemplate([{
        label: i18n.__('Hide'),
        click () {
            mainWindow.hide();
        }
    }, {
        label: i18n.__('Quit'),
        click () {
            remote.app.quit();
        }
    }]);

    if (!mainWindow.isMinimized() && !mainWindow.isVisible()) {
        _tray.setContextMenu(contextMenuShow);
    } else {
        _tray.setContextMenu(contextMenuHide);
    }

    const onShow = function () {
        _tray.setContextMenu(contextMenuHide);
    };

    const onHide = function () {
        _tray.setContextMenu(contextMenuShow);
    };

    mainWindow.on('show', onShow);
    mainWindow.on('restore', onShow);

    mainWindow.on('hide', onHide);
    mainWindow.on('minimize', onHide);

    _tray.setToolTip(remote.app.getName());

    _tray.on('right-click', function (e, b) {
        _tray.popUpContextMenu(undefined, b);
    });

    _tray.on('click', () => {
        if (mainWindow.isVisible()) {
            return mainWindow.hide();
        }

        mainWindow.show();
    });

    mainWindow.destroyTray = function () {
        mainWindow.removeListener('show', onShow);
        mainWindow.removeListener('hide', onHide);
        _tray.destroy();
    };
}

function showTrayAlert (badge, status = 'online') {
    if (mainWindow.tray === null || mainWindow.tray === undefined) {
        return;
    }
    mainWindow.tray.setImage(getTrayImagePath(badge));

    if (!mainWindow.isFocused()) {
        mainWindow.flashFrame(badge.showAlert);
    }

    if (process.platform === 'win32') {
        if (badge.showAlert) {
            mainWindow.webContents.send('render-taskbar-icon', badge.count);
        } else {
            mainWindow.setOverlayIcon(null, '');
        }
    }

    if (process.platform === 'darwin') {
        let countColor = messageCountColor['black'];
        if (remote.systemPreferences.isDarkMode()) {
            countColor = messageCountColor['white'];
        }
        mainWindow.tray.setTitle(`${statusBullet[status]} ${countColor}${badge.title}`);
        remote.app.dock.setBadge(badge.title);
    }

    if (process.platform === 'linux') {
        remote.app.setBadgeCount(badge.count);
    }
}

function removeAppTray () {
    mainWindow.destroyTray();
}

function toggle () {
    if (localStorage.getItem('hideTray') === 'true') {
        createAppTray();
        localStorage.setItem('hideTray', 'false');
    } else {
        removeAppTray();
        localStorage.setItem('hideTray', 'true');
    }
}

if (localStorage.getItem('hideTray') !== 'true') {
    createAppTray();
}

export default {
    showTrayAlert,
    toggle
};
<commit_msg>fixed badge 0 (#818)<commit_after>'use strict';

import { remote } from 'electron';
import path from 'path';
import i18n from '../i18n/index.js';

const { Tray, Menu } = remote;

const mainWindow = remote.getCurrentWindow();

const icons = {
    win32: {
        dir: 'windows'
    },
    linux: {
        dir: 'linux'
    },
    darwin: {
        dir: 'osx'
    }
};

const statusBullet = {
    online: '\u001B[32m•',
    away: '\u001B[33m•',
    busy: '\u001B[31m•',
    offline: '\u001B[30m•'
};

const messageCountColor = {
    white: '\u001B[37m',
    black: '\u001B[0m'
};

function getTrayImagePath (badge) {
    let iconFilename;
    if (badge.title === '•') {
        iconFilename = "icon-tray-dot";
    } else if (badge.count > 0) {
        if (badge.count > 9) {
            iconFilename = "icon-tray-9plus";
        } else {
            iconFilename = `icon-tray-${badge.count}`;
        }
    } else if (badge.showAlert) {
        iconFilename = "icon-tray-alert";
    } else {
        iconFilename = "icon-tray-Template";
    }

    if (process.platform === 'win32') {
        iconFilename += ".ico";
    } else {
        iconFilename += ".png";
    }

    return path.join(__dirname, 'images', icons[process.platform].dir, iconFilename);
}

function createAppTray () {
    const _tray = new Tray(getTrayImagePath({title:'',count:0,showAlert:false}));
    mainWindow.tray = _tray;

    const contextMenuShow = Menu.buildFromTemplate([{
        label: i18n.__('Show'),
        click () {
            mainWindow.show();
        }
    }, {
        label: i18n.__('Quit'),
        click () {
            remote.app.quit();
        }
    }]);

    const contextMenuHide = Menu.buildFromTemplate([{
        label: i18n.__('Hide'),
        click () {
            mainWindow.hide();
        }
    }, {
        label: i18n.__('Quit'),
        click () {
            remote.app.quit();
        }
    }]);

    if (!mainWindow.isMinimized() && !mainWindow.isVisible()) {
        _tray.setContextMenu(contextMenuShow);
    } else {
        _tray.setContextMenu(contextMenuHide);
    }

    const onShow = function () {
        _tray.setContextMenu(contextMenuHide);
    };

    const onHide = function () {
        _tray.setContextMenu(contextMenuShow);
    };

    mainWindow.on('show', onShow);
    mainWindow.on('restore', onShow);

    mainWindow.on('hide', onHide);
    mainWindow.on('minimize', onHide);

    _tray.setToolTip(remote.app.getName());

    _tray.on('right-click', function (e, b) {
        _tray.popUpContextMenu(undefined, b);
    });

    _tray.on('click', () => {
        if (mainWindow.isVisible()) {
            return mainWindow.hide();
        }

        mainWindow.show();
    });

    mainWindow.destroyTray = function () {
        mainWindow.removeListener('show', onShow);
        mainWindow.removeListener('hide', onHide);
        _tray.destroy();
    };
}

function showTrayAlert (badge, status = 'online') {
    if (mainWindow.tray === null || mainWindow.tray === undefined) {
        return;
    }
    mainWindow.tray.setImage(getTrayImagePath(badge));
    const hasMentions = badge.showAlert && badge.count > 0;

    if (!mainWindow.isFocused()) {
        mainWindow.flashFrame(hasMentions);
    }

    if (process.platform === 'win32') {
        if (hasMentions) {
            mainWindow.webContents.send('render-taskbar-icon', badge.count);
        } else {
            mainWindow.setOverlayIcon(null, '');
        }
    }

    if (process.platform === 'darwin') {
        let countColor = messageCountColor['black'];
        if (remote.systemPreferences.isDarkMode()) {
            countColor = messageCountColor['white'];
        }
        mainWindow.tray.setTitle(`${statusBullet[status]} ${countColor}${badge.title}`);
        remote.app.dock.setBadge(badge.title);
    }

    if (process.platform === 'linux') {
        remote.app.setBadgeCount(badge.count);
    }
}

function removeAppTray () {
    mainWindow.destroyTray();
}

function toggle () {
    if (localStorage.getItem('hideTray') === 'true') {
        createAppTray();
        localStorage.setItem('hideTray', 'false');
    } else {
        removeAppTray();
        localStorage.setItem('hideTray', 'true');
    }
}

if (localStorage.getItem('hideTray') !== 'true') {
    createAppTray();
}

export default {
    showTrayAlert,
    toggle
};
<|endoftext|>


<commit_before>import _ from 'underscore';

// Some utility functions missing from underscore
function sum(list, summer) {
  const fn = _.iteratee(summer);
  return _.reduce(list, function(memo, val, index) {
    return memo + fn(val, index);
  }, 0);
}

// Count the number of "true" values
function count(list, predicate) {
  const fn = _.iteratee(predicate);
  return sum(list, function(item, index) {
    return fn(item, index) ? 1 : 0;
  });
}

// A replacement for _.compact(_.flatten(_.map(l, fn))) that is more efficient.
function flatMap(list, fx) {
  const fn = _.iteratee(fx);
  const res = [];
  list.forEach((item) => {
    const resList = fn(item);
    if (resList) {
      resList.forEach((resItem) => {
        if (resItem !== undefined && resItem !== null) {
          res.push(resItem);
        }
      });
    }
  });
  return res;
}

module.exports = { sum, count, flatMap };
<commit_msg>Export from util?<commit_after>import _ from 'underscore';

// Some utility functions missing from underscore
function sum(list, summer) {
  const fn = _.iteratee(summer);
  return _.reduce(list, function(memo, val, index) {
    return memo + fn(val, index);
  }, 0);
}

// Count the number of "true" values
function count(list, predicate) {
  const fn = _.iteratee(predicate);
  return sum(list, function(item, index) {
    return fn(item, index) ? 1 : 0;
  });
}

// A replacement for _.compact(_.flatten(_.map(l, fn))) that is more efficient.
function flatMap(list, fx) {
  const fn = _.iteratee(fx);
  const res = [];
  list.forEach((item) => {
    const resList = fn(item);
    if (resList) {
      resList.forEach((resItem) => {
        if (resItem !== undefined && resItem !== null) {
          res.push(resItem);
        }
      });
    }
  });
  return res;
}

export { sum, count, flatMap };
<|endoftext|>


<commit_before>// ~ Byte stream

var second = 0;
var count = 0;
var sum = 0;
var debugInterval = null;

medical.stream = {

    MRI_DEFAULT_OPTION : {
        rotationX : 0,
        rotationY : 0,
        positionZ : 3.0
    },

    REQUEST_TYPE : {
        START : 1,
        ADAPTIVE : 2,
        CHANGE : 3
    },

    RENDERING_TYPE : {
        VOLUME : 1,
        MIP : 2,
        MRI : 3
    },

    MRI_TYPE : {
        X : 1,
        Y : 2,
        Z : 3
    },

    $sthis : null,
    url : 'ws://'+host+':9000',
    client : null,
    buffer : null,
    sendOption : null,

    mriX_option : {
        transferScaleX : 0.0,
        transferScaleY : 0.0,
        transferScaleZ : 0.0
    },
    mriY_option : {
        transferScaleX : 0.0,
        transferScaleY : 0.0,
        transferScaleZ : 0.0
    },
    mriZ_option : {
        transferScaleX : 0.0,
        transferScaleY : 0.0,
        transferScaleZ : 0.0
    },

    run : function(){
        $sthis = this;
        $sthis.sendOption = {
            request_type : $sthis.REQUEST_TYPE.START,
            rendering_type : $sthis.RENDERING_TYPE.VOLUME,
            volumePn : accessInfo.volumePn,
            frame : 0,
            brightness : 1.0,
            positionZ : 3.0,
            transferOffset : 0.0,
            rotationX : 0,
            rotationY : 0,
            transferScaleX : 0.0,
            transferScaleY : 0.0,
            transferScaleZ : 0.0,
            mriType : $sthis.MRI_TYPE.X
        };

        $sthis.client = new BinaryClient($sthis.url);
        $sthis.on();
    },

    send : function(){
        $sthis.makeBuffer();
        $sthis.client.send($sthis.buffer);
    },

    makeBuffer : function(){
        $sthis.buffer = new ArrayBuffer(52);
        var x = new Float32Array($sthis.buffer);
        x[0] = $sthis.sendOption.request_type;
        x[1] = $sthis.sendOption.volumePn;
        x[2] = $sthis.sendOption.frame;
        x[3] = $sthis.sendOption.rendering_type;
        x[4] = $sthis.sendOption.brightness;
        x[5] = $sthis.sendOption.positionZ;
        x[6] = $sthis.sendOption.transferOffset;
        x[7] = $sthis.sendOption.rotationX;
        x[8] = $sthis.sendOption.rotationY;
        x[9] = $sthis.sendOption.transferScaleX;
        x[10] = $sthis.sendOption.transferScaleY;
        x[11] = $sthis.sendOption.transferScaleZ;
        x[12] = $sthis.sendOption.mriType;
    },

    on : function(){

        $sthis.client.on('stream', function(stream, meta){

            var parts = [];

            stream.on('data', function(data){
                parts.push(data);
            });

            stream.on('end', function(){
                var url = (window.URL || window.webkitURL).createObjectURL(new Blob(parts));
                medical.connect.document.view.attr('src', url);

                count++;
                if(debugInterval === null){
                    debugInterval = setInterval($sthis.debug, 1000);
                }
            });
        });
    },

    request : function(){
        $sthis.sendOption.request_type = $sthis.REQUEST_TYPE.START;
        $sthis.send();
    },

    adaptive : function(){
        $sthis.sendOption.request_type = $sthis.REQUEST_TYPE.ADAPTIVE;
        $sthis.sendOption.frame = sum;
        $sthis.send();
    },

    debug : function(){

        if(count === 0){
            clearInterval(debugInterval);
            debugInterval = undefined;
        }

        second+=1;
        sum+=count;
        $('.debug_wrap').append('<p> sec: '+ second + ' c: ' +count+'</p>');
        if(second % 3 === 0 && medical.connect.isConnect) {
            $sthis.adaptive();
            sum = 0;
        }
        count = 0;
    }
};<commit_msg>modify adaptive Interval<commit_after>// ~ Byte stream
medical.stream = {

    MRI_DEFAULT_OPTION : {
        rotationX : 0,
        rotationY : 0,
        positionZ : 3.0
    },

    REQUEST_TYPE : {
        START : 1,
        ADAPTIVE : 2,
        CHANGE : 3
    },

    RENDERING_TYPE : {
        VOLUME : 1,
        MIP : 2,
        MRI : 3
    },

    MRI_TYPE : {
        X : 1,
        Y : 2,
        Z : 3
    },

    $sthis : null,
    url : 'ws://'+host+':9000',
    client : null,
    buffer : null,
    sendOption : null,

    mriX_option : {
        transferScaleX : 0.0,
        transferScaleY : 0.0,
        transferScaleZ : 0.0
    },
    mriY_option : {
        transferScaleX : 0.0,
        transferScaleY : 0.0,
        transferScaleZ : 0.0
    },
    mriZ_option : {
        transferScaleX : 0.0,
        transferScaleY : 0.0,
        transferScaleZ : 0.0
    },

    run : function(){
        $sthis = this;
        $sthis.sendOption = {
            request_type : $sthis.REQUEST_TYPE.START,
            rendering_type : $sthis.RENDERING_TYPE.VOLUME,
            volumePn : accessInfo.volumePn,
            frame : 0,
            brightness : 1.0,
            positionZ : 3.0,
            transferOffset : 0.0,
            rotationX : 0,
            rotationY : 0,
            transferScaleX : 0.0,
            transferScaleY : 0.0,
            transferScaleZ : 0.0,
            mriType : $sthis.MRI_TYPE.X
        };

        $sthis.client = new BinaryClient($sthis.url);
        $sthis.on();
    },

    send : function(){
        $sthis.makeBuffer();
        $sthis.client.send($sthis.buffer);
    },

    makeBuffer : function(){
        $sthis.buffer = new ArrayBuffer(52);
        var x = new Float32Array($sthis.buffer);
        x[0] = $sthis.sendOption.request_type;
        x[1] = $sthis.sendOption.volumePn;
        x[2] = $sthis.sendOption.frame;
        x[3] = $sthis.sendOption.rendering_type;
        x[4] = $sthis.sendOption.brightness;
        x[5] = $sthis.sendOption.positionZ;
        x[6] = $sthis.sendOption.transferOffset;
        x[7] = $sthis.sendOption.rotationX;
        x[8] = $sthis.sendOption.rotationY;
        x[9] = $sthis.sendOption.transferScaleX;
        x[10] = $sthis.sendOption.transferScaleY;
        x[11] = $sthis.sendOption.transferScaleZ;
        x[12] = $sthis.sendOption.mriType;
    },

    on : function(){

        $sthis.client.on('stream', function(stream, meta){

            var parts = [];

            stream.on('data', function(data){
                parts.push(data);
            });

            stream.on('end', function(){
                var url = (window.URL || window.webkitURL).createObjectURL(new Blob(parts));
                medical.connect.document.view.attr('src', url);

                $sthis.adaptiveOption.frame++;
                if($sthis.adaptiveOption.interval === null)
                    $sthis.adaptiveOption.interval = setInterval($sthis.adaptiveInterval, 1000);

            });
        });
    },

    request : function(){
        $sthis.sendOption.request_type = $sthis.REQUEST_TYPE.START;
        $sthis.send();
    },

    adaptiveOption : {
        interval : null,
        frame : 0,
        sum : 0,
        elapsedTime : 0
    },

    adaptive : function(){
        $sthis.sendOption.request_type = $sthis.REQUEST_TYPE.ADAPTIVE;
        $sthis.sendOption.frame = $sthis.adaptiveOption.sum;
        $sthis.send();
    },

    adaptiveInterval : function(){

        if($sthis.adaptiveOption.frame === 0){
            clearInterval($sthis.adaptiveOption.interval);
            $sthis.adaptiveOption.interval = null;
        }

        $sthis.adaptiveOption.elapsedTime += 1;
        $sthis.adaptiveOption.sum += $sthis.adaptiveOption.frame;

        if( ( $sthis.adaptiveOption.elapsedTime % 3 === 0 ) && medical.connect.isConnect) {
            $sthis.adaptive();
            $sthis.adaptiveOption.elapsedTime = 0;
        }

        $sthis.adaptiveOption.frame = 0;
    }

};<|endoftext|>


<commit_before>import get from 'lodash/fp/get'
import last from 'lodash/fp/last'

import { searchableTextFields } from 'src/page-analysis'

import { findVisits, addVisitsContext } from './find-visits'

// Search by keyword query, returning all docs if no query is given
export async function filterVisitsByQuery({
    query,
    startDate,
    endDate,
    skipUntil,
    limit = 10,
    includeContext = false,
}) {
    if (limit <= 0) {
        return {
            rows: [],
            resultsExhausted: true,
        }
    }
    if (query === '') {
        const visitsResult = await findVisits({startDate, endDate, limit, skipUntil})
        // Note whether we reached the bottom.
        visitsResult.resultsExhausted = visitsResult.rows.length < limit
        return visitsResult
    } else {
        // Process visits batch by batch, filtering for ones that match the
        // query until we reach the requested limit or have exhausted all
        // of them.

        let rows = []
        let resultsExhausted = false
        // Number of visits we process at a time (rather arbitrary)
        const batchSize = limit * 10
        do {
            let batch = await findVisits({
                startDate,
                endDate,
                limit: batchSize,
                skipUntil,
            })
            const batchRows = batch.rows

            // Check if we reached the bottom.
            resultsExhausted = batchRows.length < batchSize

            // Next batch (or next invocation), start from the last result.
            skipUntil = (batchRows.length > 0)
                ? last(batchRows).id
                : skipUntil

            // Filter for visits to pages that contain the query words.
            const hits = batchRows.filter(
                row => pageMatchesQuery({page: row.doc.page, query})
            )

            rows = rows.concat(hits)

            // If we did not have enough hits, get and filter another batch.
        } while (rows.length < limit && !resultsExhausted)

        // If too many, apply the requested limit to the number of results.
        if (rows.length > limit) {
            rows = rows.slice(0, limit)
            resultsExhausted = false
            skipUntil = last(rows).id
        }

        let visitsResult = {
            rows,
            // Remember the last docId, to continue from there when more results
            // are requested.
            searchedUntil: skipUntil,
            resultsExhausted,
        }

        if (includeContext) { visitsResult = await addVisitsContext({visitsResult}) }

        return visitsResult
    }
}

// We just use a simple literal word filter for now. No index, no ranking.
function pageMatchesQuery({page, query}) {
    // Get page text fields.
    const texts = searchableTextFields.map(fieldName => get(fieldName)(page))
        .filter(text => text) // remove undefined fields
        .map(text => text.toLowerCase())

    // Test if every word in the query is present in at least one text field.
    const queryWords = query.toLowerCase().trim().split(/s+/)
    return queryWords.every(word =>
        texts.some(text => text.includes(word))
    )
}
<commit_msg>Fix searching on non-string fields.<commit_after>import get from 'lodash/fp/get'
import last from 'lodash/fp/last'

import { searchableTextFields } from 'src/page-analysis'

import { findVisits, addVisitsContext } from './find-visits'

// Search by keyword query, returning all docs if no query is given
export async function filterVisitsByQuery({
    query,
    startDate,
    endDate,
    skipUntil,
    limit = 10,
    includeContext = false,
}) {
    if (limit <= 0) {
        return {
            rows: [],
            resultsExhausted: true,
        }
    }
    if (query === '') {
        const visitsResult = await findVisits({startDate, endDate, limit, skipUntil})
        // Note whether we reached the bottom.
        visitsResult.resultsExhausted = visitsResult.rows.length < limit
        return visitsResult
    } else {
        // Process visits batch by batch, filtering for ones that match the
        // query until we reach the requested limit or have exhausted all
        // of them.

        let rows = []
        let resultsExhausted = false
        // Number of visits we process at a time (rather arbitrary)
        const batchSize = limit * 10
        do {
            let batch = await findVisits({
                startDate,
                endDate,
                limit: batchSize,
                skipUntil,
            })
            const batchRows = batch.rows

            // Check if we reached the bottom.
            resultsExhausted = batchRows.length < batchSize

            // Next batch (or next invocation), start from the last result.
            skipUntil = (batchRows.length > 0)
                ? last(batchRows).id
                : skipUntil

            // Filter for visits to pages that contain the query words.
            const hits = batchRows.filter(
                row => pageMatchesQuery({page: row.doc.page, query})
            )

            rows = rows.concat(hits)

            // If we did not have enough hits, get and filter another batch.
        } while (rows.length < limit && !resultsExhausted)

        // If too many, apply the requested limit to the number of results.
        if (rows.length > limit) {
            rows = rows.slice(0, limit)
            resultsExhausted = false
            skipUntil = last(rows).id
        }

        let visitsResult = {
            rows,
            // Remember the last docId, to continue from there when more results
            // are requested.
            searchedUntil: skipUntil,
            resultsExhausted,
        }

        if (includeContext) { visitsResult = await addVisitsContext({visitsResult}) }

        return visitsResult
    }
}

// We just use a simple literal word filter for now. No index, no ranking.
function pageMatchesQuery({page, query}) {
    // Get page text fields.
    const texts = searchableTextFields.map(fieldName => get(fieldName)(page))
        .filter(text => text) // remove undefined fields
        .map(text => text.toString().toLowerCase())

    // Test if every word in the query is present in at least one text field.
    const queryWords = query.toLowerCase().trim().split(/s+/)
    return queryWords.every(word =>
        texts.some(text => text.includes(word))
    )
}
<|endoftext|>


<commit_before>import { updateApp } from "./update-app";

import {
    GREY_EDGES_DASHED_MENU,
    GREY_EDGES_DASHED_SIDEBAR,
    GREY_EDGE_THRESHOLD_MENU,
    GREY_EDGE_THRESHOLD_SLIDER_SIDEBAR
} from "./constants";

import { setupLoadAndImportHandlers } from "./setup-load-and-import-handlers";

export const setupHandlers = grnState => {
    setupLoadAndImportHandlers(grnState);

    $(GREY_EDGES_DASHED_SIDEBAR).change(() => {
        grnState.dashedLine = $(GREY_EDGES_DASHED_SIDEBAR).prop("checked");
        updateApp(grnState);
    });

    $(GREY_EDGES_DASHED_MENU).click(() => {
        grnState.dashedLine = !$(GREY_EDGES_DASHED_MENU).prop("checked");
        updateApp(grnState);
    });

    $("#normalization-button").click(() => {
        grnState.normalizationMax = $("#normalization-max").val();
        updateApp(grnState);
    });

    $("#reset-normalization-factor-menu, #resetNormalizationButton").click(() => {
        grnState.normalizationMax = grnState.resetNormalizationMax;
        updateApp(grnState);
    });

    $("#edge-weight-normalization-factor-menu").change(() => {
        grnState.normalizationMax = $("#edge-weight-normalization-factor-menu").val();
        updateApp(grnState);
    });

    $(GREY_EDGE_THRESHOLD_MENU).change(() => {
        grnState.grayEdgeThreshold = Math.round(($(GREY_EDGE_THRESHOLD_MENU).val()));
        updateApp(grnState);
    });

    $(GREY_EDGE_THRESHOLD_SLIDER_SIDEBAR).change(() => {
        grnState.grayEdgeThreshold = Math.round(($(GREY_EDGE_THRESHOLD_SLIDER_SIDEBAR).val() * 100));
        updateApp(grnState);
    });
};
<commit_msg>parentheses<commit_after>import { updateApp } from "./update-app";

import {
    GREY_EDGES_DASHED_MENU,
    GREY_EDGES_DASHED_SIDEBAR,
    GREY_EDGE_THRESHOLD_MENU,
    GREY_EDGE_THRESHOLD_SLIDER_SIDEBAR
} from "./constants";

import { setupLoadAndImportHandlers } from "./setup-load-and-import-handlers";

export const setupHandlers = grnState => {
    setupLoadAndImportHandlers(grnState);

    $(GREY_EDGES_DASHED_SIDEBAR).change(() => {
        grnState.dashedLine = $(GREY_EDGES_DASHED_SIDEBAR).prop("checked");
        updateApp(grnState);
    });

    $(GREY_EDGES_DASHED_MENU).click(() => {
        grnState.dashedLine = !$(GREY_EDGES_DASHED_MENU).prop("checked");
        updateApp(grnState);
    });

    $("#normalization-button").click(() => {
        grnState.normalizationMax = $("#normalization-max").val();
        updateApp(grnState);
    });

    $("#reset-normalization-factor-menu, #resetNormalizationButton").click(() => {
        grnState.normalizationMax = grnState.resetNormalizationMax;
        updateApp(grnState);
    });

    $("#edge-weight-normalization-factor-menu").change(() => {
        grnState.normalizationMax = $("#edge-weight-normalization-factor-menu").val();
        updateApp(grnState);
    });

    $(GREY_EDGE_THRESHOLD_MENU).change(() => {
        grnState.grayEdgeThreshold = Math.round($(GREY_EDGE_THRESHOLD_MENU).val());
        updateApp(grnState);
    });

    $(GREY_EDGE_THRESHOLD_SLIDER_SIDEBAR).change(() => {
        grnState.grayEdgeThreshold = Math.round($(GREY_EDGE_THRESHOLD_SLIDER_SIDEBAR).val() * 100);
        updateApp(grnState);
    });
};
<|endoftext|>