<commit_before>let tabs = {};
let currentTab;
let jitsiVisible = false;

/**
 * Gets the base firebase reference for this workspace.
 */
function getFirebaseRef() {
  const workspaceID = getParam("workspaceID");
  if (workspaceID !== null) {
    return firebase.database().ref().child(workspaceID);
  } else {
    // If we were not given a workspace ID redirect to the home page.
    window.location.href = "/";
  }
}

/**
 * Scroll event handler that allows users to scroll tabs using
 * a scroll wheel.
 */
function scrollTabs(event) {
  // Only translate vertical scrolling to horizontal scrolling.
  if (event.deltaY) {
    this.scrollLeft += (event.deltaY);
    event.preventDefault();
  }
}

/**
 * Switches the current tab.
 * @param {String} tab name of the tab to switch to .
 */
function switchTab(tab) {
  if (currentTab !== tab) {
    if (tabs[currentTab]) {
      tabs[currentTab].editorContainer.classList.add("hidden");
      tabs[currentTab].tabButton.classList.remove("active-tab");
      tabs[currentTab].tabButton.classList.add("inactive-tab");
    }

    tabs[tab].editorContainer.classList.remove("hidden");
    tabs[tab].tabButton.classList.remove("inactive-tab");
    tabs[tab].tabButton.classList.add("active-tab");

    tabs[tab].editor.layout();

    currentTab = tab;
  }
}

/**
 * Firebase does not accept the folowing characters:
 * .$[]#/
 * We must encode them to store them in realtime database.
 * @param {String} filename  filename to encode.
 */
function encodeFileName(filename) {
  return encodeURIComponent(filename).replace(/\./g, '%2E');
}

/**
 * Decodes a file name from the realtime database
 * @param {String} filename  filename to decode.
 */
function decodeFileName(filename) {
  return decodeURIComponent(filename.replace(/%2E/g, "."));
}

/**
 * Creates a new tab with the given filename and the given contents.
 * @param {String} filename The filename for the tab.
 * @param {String} contents  The contents of the file. If non null the
 * tab will be initialized with this string.
 */
function createNewTab(filename, contents) {
  if (!tabs[filename]) {
    // Add filename to tabs immediately so that the tab is not added
    // twice in the firebase child_added callback.
    tabs[filename] = {};

    const tab = document.createElement("button");
    tab.classList.add("inactive-tab", "tab");
    tab.innerText = filename;

    tab.onclick = (event) => {
      switchTab(filename);
    };

    const firepadContainer = document.createElement("div");
    firepadContainer.classList.add("hidden", "firepad-container");

    const fileExtension = "." + filename.split('.').pop();

    const languages = monaco.languages.getLanguages().filter((lang) => lang.extensions.includes(fileExtension));

    const language = languages.length >= 1 ? languages[0].id : "plaintext";

    const editor = monaco.editor.create(firepadContainer, {
      language: language,
      theme: "vs-dark"
    });

    //Use LF
    editor.getModel().setEOL("\n");

    const firepad = Firepad.fromMonaco(getFirebaseRef().child("files").child(encodeFileName(filename)), editor);

    if (contents !== null) {
      firepad.on("ready", () => {
        firepad.setText(contents);
      });
    }

    tabs[filename] = {
      editor: editor,
      firepad: firepad,
      tabButton: tab,
      editorContainer: firepadContainer
    };

    document.getElementById("tabs-container").appendChild(tab);
    document.getElementById("firepads").appendChild(firepadContainer);
  } else if (contents !== null) {
    tabs[filename].firepad.setText(contents);
  }

}

function leftJitsi() {
  document.getElementById("jitsi-window").innerHTML = "";
  document.getElementById("jitsi-container").classList.add("hidden");

  const button = document.getElementById("jitsi-join");
  button.innerText = "Join Meeting";
  button.onclick = addJitsiWindow;

  jitsiVisible = false;
}

function toggleJitsi() {
  const button = document.getElementById("toggleJitsiButton");
  const jitsiWindow = document.getElementById("jitsi-window");

  if (jitsiVisible) {
    jitsiWindow.classList.add("hidden");
    button.innerText = "Show Jitsi";
  } else {
    jitsiWindow.classList.remove("hidden");
    button.innerText = "Hide Jitsi";
  }

  jitsiVisible = !jitsiVisible;
}

function addJitsiWindow() { // jshint ignore:line
  jitsiVisible = true;

  const jitsiJoin = document.getElementById("jitsi-join");
  const jitsiShow = document.getElementById("toggleJitsiButton");
  document.getElementById("jitsi-container").classList.remove("hidden");

  jitsiShow.innerText = "Hide Jitsi";
  jitsiJoin.innerText = "Leave Meeting";

  const parent = document.getElementById("jitsi-window");

  const api = new JitsiMeetExternalAPI("meet.jit.si", {
    roomName: getParam("workspaceID"),
    height: 300,
    width: 500,
    parentNode: parent
  });

  jitsiJoin.onclick = () => api.executeCommand('hangup');

  api.addEventListener("videoConferenceLeft", leftJitsi);
}

document.addEventListener("DOMContentLoaded", () => {
  document.getElementById("tabs-container").onwheel = scrollTabs;

  require.config({ paths: {'vs': 'https://unpkg.com/monaco-editor@latest/min/vs'}});
  require(['vs/editor/editor.main'], function() {
    // Once the monaco library is loaded, we can start uploading files.
    const hiddenElements = document.getElementsByClassName("initially-hidden");

    for (let e of hiddenElements) {
      e.classList.remove("initially-hidden");
    }

    getFirebaseRef().child("files").on("child_added", (snapshot) => {
      const filename = decodeFileName(snapshot.key);
      createNewTab(filename, null);
      if (!currentTab) {
        switchTab(filename);
      }
    });
  });
});

window.onresize = () => {
  if (currentTab) {
    tabs[currentTab].editor.layout();
  }
};

/**
 * Called when the upload files button is clicked.
 */
function uploadFiles() {
  document.getElementById("upload-files").click();
}

/**
 * Called when files have been uploaded.
 */
async function filesUploaded() { // jshint ignore:line
  const files = document.getElementById("upload-files").files;

  for(var file of files) {
    // Convert to LF if in CRLF or CR
    let contents =  (await file.text()).replace(/\r\n?/g, "\n"); // jshint ignore:line
    createNewTab(file.name, contents);
  }

  switchTab(files[0].name);
}

function downloadFiles() {
  const downloadButton = document.getElementById("downloadButton");
  downloadButton.classList.add("download-in-progress");
  downloadButton.disabled = true;
  getToken().then(tok => {
    fetch(`/workspace/queueDownload?workspaceID=${getParam("workspaceID")}&idToken=${tok}`)
    .then(resp => resp.text()).then(downloadID => {
      getFirebaseRef().child("downloads").child(downloadID).on("value", snap => {
        if (snap.val() !== null) {
          const downloadLink = document.getElementById("downloadLink");
          downloadLink.href = `/workspace/downloadWorkspace?filename=${snap.val()}`;
          downloadLink.click();
          getFirebaseRef().child("downloads").child(downloadID).off("value");
          downloadButton.classList.remove("download-in-progress");
          downloadButton.disabled = false;
        }
      });
    });
  });
}<commit_msg>Set jitsi displayName from firebase auth.<commit_after>let tabs = {};
let currentTab;
let jitsiVisible = false;

/**
 * Gets the base firebase reference for this workspace.
 */
function getFirebaseRef() {
  const workspaceID = getParam("workspaceID");
  if (workspaceID !== null) {
    return firebase.database().ref().child(workspaceID);
  } else {
    // If we were not given a workspace ID redirect to the home page.
    window.location.href = "/";
  }
}

/**
 * Scroll event handler that allows users to scroll tabs using
 * a scroll wheel.
 */
function scrollTabs(event) {
  // Only translate vertical scrolling to horizontal scrolling.
  if (event.deltaY) {
    this.scrollLeft += (event.deltaY);
    event.preventDefault();
  }
}

/**
 * Switches the current tab.
 * @param {String} tab name of the tab to switch to .
 */
function switchTab(tab) {
  if (currentTab !== tab) {
    if (tabs[currentTab]) {
      tabs[currentTab].editorContainer.classList.add("hidden");
      tabs[currentTab].tabButton.classList.remove("active-tab");
      tabs[currentTab].tabButton.classList.add("inactive-tab");
    }

    tabs[tab].editorContainer.classList.remove("hidden");
    tabs[tab].tabButton.classList.remove("inactive-tab");
    tabs[tab].tabButton.classList.add("active-tab");

    tabs[tab].editor.layout();

    currentTab = tab;
  }
}

/**
 * Firebase does not accept the folowing characters:
 * .$[]#/
 * We must encode them to store them in realtime database.
 * @param {String} filename  filename to encode.
 */
function encodeFileName(filename) {
  return encodeURIComponent(filename).replace(/\./g, '%2E');
}

/**
 * Decodes a file name from the realtime database
 * @param {String} filename  filename to decode.
 */
function decodeFileName(filename) {
  return decodeURIComponent(filename.replace(/%2E/g, "."));
}

/**
 * Creates a new tab with the given filename and the given contents.
 * @param {String} filename The filename for the tab.
 * @param {String} contents  The contents of the file. If non null the
 * tab will be initialized with this string.
 */
function createNewTab(filename, contents) {
  if (!tabs[filename]) {
    // Add filename to tabs immediately so that the tab is not added
    // twice in the firebase child_added callback.
    tabs[filename] = {};

    const tab = document.createElement("button");
    tab.classList.add("inactive-tab", "tab");
    tab.innerText = filename;

    tab.onclick = (event) => {
      switchTab(filename);
    };

    const firepadContainer = document.createElement("div");
    firepadContainer.classList.add("hidden", "firepad-container");

    const fileExtension = "." + filename.split('.').pop();

    const languages = monaco.languages.getLanguages().filter((lang) => lang.extensions.includes(fileExtension));

    const language = languages.length >= 1 ? languages[0].id : "plaintext";

    const editor = monaco.editor.create(firepadContainer, {
      language: language,
      theme: "vs-dark"
    });

    //Use LF
    editor.getModel().setEOL("\n");

    const firepad = Firepad.fromMonaco(getFirebaseRef().child("files").child(encodeFileName(filename)), editor);

    if (contents !== null) {
      firepad.on("ready", () => {
        firepad.setText(contents);
      });
    }

    tabs[filename] = {
      editor: editor,
      firepad: firepad,
      tabButton: tab,
      editorContainer: firepadContainer
    };

    document.getElementById("tabs-container").appendChild(tab);
    document.getElementById("firepads").appendChild(firepadContainer);
  } else if (contents !== null) {
    tabs[filename].firepad.setText(contents);
  }

}

function leftJitsi() {
  document.getElementById("jitsi-window").innerHTML = "";
  document.getElementById("jitsi-container").classList.add("hidden");

  const button = document.getElementById("jitsi-join");
  button.innerText = "Join Meeting";
  button.onclick = addJitsiWindow;

  jitsiVisible = false;
}

function toggleJitsi() {
  const button = document.getElementById("toggleJitsiButton");
  const jitsiWindow = document.getElementById("jitsi-window");

  if (jitsiVisible) {
    jitsiWindow.classList.add("hidden");
    button.innerText = "Show Jitsi";
  } else {
    jitsiWindow.classList.remove("hidden");
    button.innerText = "Hide Jitsi";
  }

  jitsiVisible = !jitsiVisible;
}

function addJitsiWindow() { // jshint ignore:line
  jitsiVisible = true;

  const jitsiJoin = document.getElementById("jitsi-join");
  const jitsiShow = document.getElementById("toggleJitsiButton");
  document.getElementById("jitsi-container").classList.remove("hidden");

  jitsiShow.innerText = "Hide Jitsi";
  jitsiJoin.innerText = "Leave Meeting";

  const parent = document.getElementById("jitsi-window");

  const api = new JitsiMeetExternalAPI("meet.jit.si", {
    roomName: getParam("workspaceID"),
    height: 300,
    width: 500,
    parentNode: parent
  });

  jitsiJoin.onclick = () => api.executeCommand('hangup');

  api.addEventListener("videoConferenceLeft", leftJitsi);
  api.executeCommand("displayName", firebase.auth().currentUser.displayName);
}

document.addEventListener("DOMContentLoaded", () => {
  document.getElementById("tabs-container").onwheel = scrollTabs;

  require.config({ paths: {'vs': 'https://unpkg.com/monaco-editor@latest/min/vs'}});
  require(['vs/editor/editor.main'], function() {
    // Once the monaco library is loaded, we can start uploading files.
    const hiddenElements = document.getElementsByClassName("initially-hidden");

    for (let e of hiddenElements) {
      e.classList.remove("initially-hidden");
    }

    getFirebaseRef().child("files").on("child_added", (snapshot) => {
      const filename = decodeFileName(snapshot.key);
      createNewTab(filename, null);
      if (!currentTab) {
        switchTab(filename);
      }
    });
  });
});

window.onresize = () => {
  if (currentTab) {
    tabs[currentTab].editor.layout();
  }
};

/**
 * Called when the upload files button is clicked.
 */
function uploadFiles() {
  document.getElementById("upload-files").click();
}

/**
 * Called when files have been uploaded.
 */
async function filesUploaded() { // jshint ignore:line
  const files = document.getElementById("upload-files").files;

  for(var file of files) {
    // Convert to LF if in CRLF or CR
    let contents =  (await file.text()).replace(/\r\n?/g, "\n"); // jshint ignore:line
    createNewTab(file.name, contents);
  }

  switchTab(files[0].name);
}

function downloadFiles() {
  const downloadButton = document.getElementById("downloadButton");
  downloadButton.classList.add("download-in-progress");
  downloadButton.disabled = true;
  getToken().then(tok => {
    fetch(`/workspace/queueDownload?workspaceID=${getParam("workspaceID")}&idToken=${tok}`)
    .then(resp => resp.text()).then(downloadID => {
      getFirebaseRef().child("downloads").child(downloadID).on("value", snap => {
        if (snap.val() !== null) {
          const downloadLink = document.getElementById("downloadLink");
          downloadLink.href = `/workspace/downloadWorkspace?filename=${snap.val()}`;
          downloadLink.click();
          getFirebaseRef().child("downloads").child(downloadID).off("value");
          downloadButton.classList.remove("download-in-progress");
          downloadButton.disabled = false;
        }
      });
    });
  });
}<|endoftext|>


<commit_before>import GitHubAPI from 'github'
import config from 'config'
import ScreenshotBucket from 'server/models/ScreenshotBucket'

async function fallbackToMaster(repository) {
  const bucket = await ScreenshotBucket.query()
    .where({
      branch: 'master',
      repositoryId: repository.id,
    })
    .orderBy('id', 'desc')
    .limit(1)
    .first()

  return bucket || null
}

async function baseCompare({
  baseCommit,
  compareCommit,
  repository,
  perPage = 30,
}) {
  const user = await repository.getUsers().limit(1).first()

  if (!user) {
    return {
      user: null,
      compareCommitFound: false,
      baseScreenshotBucket: null,
    }
  }

  const owner = await repository.getOwner()
  const github = new GitHubAPI({ debug: config.get('env') === 'development' })
  github.authenticate({
    type: 'oauth',
    token: user.accessToken,
  })

  // http://stackoverflow.com/questions/9179828/github-api-retrieve-all-commits-for-all-branches-for-a-repo
  // http://mikedeboer.github.io/node-github/#api-repos-getBranch
  const baseCommits = await github.repos.getCommits({
    owner: owner.login,
    repo: repository.name,
    sha: baseCommit,
    per_page: perPage,
    page: 1,
  })

  let compareCommits

  try {
    compareCommits = await github.repos.getCommits({
      owner: owner.login,
      repo: repository.name,
      sha: compareCommit,
      per_page: perPage,
      page: 1,
    })
  } catch (error) {
    if (error.status === 'Not Found') {
      return {
        user,
        compareCommitFound: false,
        baseScreenshotBucket: null,
      }
    }

    throw error
  }

  const potentialCommits = []
  const forkCommit = baseCommits.data.find((baseCommit, index) => {
    const comparingWithHimself = baseCommit.sha === compareCommit

    // We can't compare with ourself.
    if (!comparingWithHimself) {
      potentialCommits.push(baseCommit)
    }

    const found = compareCommits.data.some(compareCommit => (
      baseCommit.sha === compareCommit.sha
    ))

    // Takes the previous commit too.
    if (found && comparingWithHimself && index + 1 < baseCommits.data.length) {
      potentialCommits.push(baseCommits.data[index + 1])
    }

    return found
  })

  let baseScreenshotBucket

  if (!forkCommit) {
    baseScreenshotBucket = await fallbackToMaster(repository)
  }

  const potentialCommitShas = potentialCommits.map(commit => commit.sha)

  if (!baseScreenshotBucket) {
    const buckets = await ScreenshotBucket.query()
      .where({
        repositoryId: repository.id,
      })
      .whereIn('commit', potentialCommitShas)

    // Reverse the potentialCommits order.
    buckets.sort((bucketA, bucketB) => (
      potentialCommitShas.indexOf(bucketB.commit) -
      potentialCommitShas.indexOf(bucketA.commit)
    ))

    baseScreenshotBucket = buckets[0] || null
  }

  return {
    user,
    compareCommitFound: true,
    baseScreenshotBucket,
  }
}

export default baseCompare
<commit_msg>fix: always fallback to master<commit_after>import GitHubAPI from 'github'
import config from 'config'
import ScreenshotBucket from 'server/models/ScreenshotBucket'

async function fallbackToMaster(repository) {
  const bucket = await ScreenshotBucket.query()
    .where({
      branch: 'master',
      repositoryId: repository.id,
    })
    .orderBy('id', 'desc')
    .limit(1)
    .first()

  return bucket || null
}

async function getBaseScreenshotBucket({
  forkCommit,
  potentialCommits,
  repository,
}) {
  if (!forkCommit) {
    return fallbackToMaster(repository)
  }

  const potentialCommitShas = potentialCommits.map(commit => commit.sha)
  const buckets = await ScreenshotBucket.query()
    .where({ repositoryId: repository.id })
    .whereIn('commit', potentialCommitShas)

  // Reverse the potentialCommits order.
  buckets.sort((bucketA, bucketB) => (
    potentialCommitShas.indexOf(bucketB.commit) -
    potentialCommitShas.indexOf(bucketA.commit)
  ))

  return buckets[0] || fallbackToMaster(repository)
}

async function baseCompare({
  baseCommit,
  compareCommit,
  repository,
  perPage = 30,
}) {
  const user = await repository.getUsers().limit(1).first()

  if (!user) {
    return {
      user: null,
      compareCommitFound: false,
      baseScreenshotBucket: null,
    }
  }

  const owner = await repository.getOwner()
  const github = new GitHubAPI({ debug: config.get('env') === 'development' })
  github.authenticate({
    type: 'oauth',
    token: user.accessToken,
  })

  // http://stackoverflow.com/questions/9179828/github-api-retrieve-all-commits-for-all-branches-for-a-repo
  // http://mikedeboer.github.io/node-github/#api-repos-getBranch
  const baseCommits = await github.repos.getCommits({
    owner: owner.login,
    repo: repository.name,
    sha: baseCommit,
    per_page: perPage,
    page: 1,
  })

  let compareCommits

  try {
    compareCommits = await github.repos.getCommits({
      owner: owner.login,
      repo: repository.name,
      sha: compareCommit,
      per_page: perPage,
      page: 1,
    })
  } catch (error) {
    if (error.status === 'Not Found') {
      return {
        user,
        compareCommitFound: false,
        baseScreenshotBucket: null,
      }
    }

    throw error
  }

  const potentialCommits = []
  const forkCommit = baseCommits.data.find((baseCommit, index) => {
    const comparingWithHimself = baseCommit.sha === compareCommit

    // We can't compare with ourself.
    if (!comparingWithHimself) {
      potentialCommits.push(baseCommit)
    }

    const found = compareCommits.data.some(compareCommit => (
      baseCommit.sha === compareCommit.sha
    ))

    // Takes the previous commit too.
    if (found && comparingWithHimself && index + 1 < baseCommits.data.length) {
      potentialCommits.push(baseCommits.data[index + 1])
    }

    return found
  })

  const baseScreenshotBucket = await getBaseScreenshotBucket({
    forkCommit,
    potentialCommits,
    repository,
  })

  return {
    user,
    compareCommitFound: true,
    baseScreenshotBucket,
  }
}

export default baseCompare
<|endoftext|>


<commit_before>/*
 * == BSD2 LICENSE ==
 * Copyright (c) 2017, Tidepool Project
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the associated License, which is identical to the BSD 2-Clause
 * License as published by the Open Source Initiative at opensource.org.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the License for more details.
 *
 * You should have received a copy of the License along with this program; if
 * not, you can obtain one from Tidepool Project at tidepool.org.
 * == BSD2 LICENSE ==
 */

/* eslint-disable lodash/prefer-lodash-method */

import _ from 'lodash';

import PrintView from './PrintView';

import {
  getDeviceMeta,
  processBasalRateData,
} from '../../utils/settings/data';

import {
  bolusTitle,
} from '../../utils/settings/nonTandemData';

class SettingsPrintView extends PrintView {
  constructor(doc, data, opts) {
    super(doc, data, opts);

    this.manufacturer = _.get(data, 'source', '').toLowerCase();
    this.deviceMeta = getDeviceMeta(data, opts.timePrefs);
    this.layoutColumns = {};
  }

  render() {
    // console.log('doc', this.doc);
    console.log('data', this.data);
    console.log('deviceMeta', this.deviceMeta);
    this.doc.addPage();
    this.renderDeviceMeta();
    this.renderBasalSchedules();
    this.renderBolusDetails();
  }

  renderDeviceMeta() {
    this.doc
      .font(this.boldFont)
      .fontSize(this.defaultFontSize)
      .text(this.data.source, { continued: true })
      .font(this.font)
      .text(` Uploaded on ${this.deviceMeta.uploaded}`, { continued: true })
      .text(` › Serial Number: ${this.deviceMeta.serial}`)
      .moveDown();

    this.resetText();
  }

  renderBasalSchedules() {
    this.renderSectionHeading('Basal Rates');

    const {
      activeSchedule,
      basalSchedules,
    } = this.data;

    this.setLayoutColumns(this.chartArea.width, 3, 20);

    const tableWidth = this.layoutColumns.itemWidth;

    const tableColumns = [
      {
        id: 'start',
        header: 'Start time',
        align: 'left',
        width: tableWidth - 50,
      },
      {
        id: 'rate',
        header: 'Value',
        align: 'right',
        width: 50,
      },
    ];

    const sortedSchedules = _.sortByOrder(basalSchedules,
      [
        schedule => (schedule.name === activeSchedule ? 1 : 0),
        schedule => schedule.value.length,
        'name',
      ],
      ['desc', 'desc', 'asc']
    );

    _.each(sortedSchedules, (schedule, index) => {

      const activeColumn = index < this.layoutColumns.count
        ? index % this.layoutColumns.count
        : this.getShortestLayoutColumn();

      this.gotoLayoutColumnPosition(activeColumn);

      const note = schedule.name === activeSchedule ? 'Active at Upload' : null;

      const data = processBasalRateData(schedule);

      const heading = {
        text: schedule.name,
        subText: 'U/hr',
        note,
      };

      this.renderTableHeading(heading, {
        columnDefaults: {
          fill: {
            color: this.colors.basal,
            opacity: 0.4,
          },
          width: tableWidth,
        },
      });

      this.updateLayoutColumnPosition(activeColumn);

      this.renderTable(tableColumns, data, {
        columnDefaults: {
          zebra: true,
          headerFill: true,
        },
      });

      this.updateLayoutColumnPosition(activeColumn);
    });

    this.doc.moveDown();
  }

  renderBolusDetails() {
    this.doc.x = this.chartArea.leftEdge;
    this.doc.y = this.layoutColumns.columns[this.getLongestLayoutColumn()].y;
    this.renderSectionHeading(bolusTitle(this.manufacturer));
  }
}

export default SettingsPrintView;
<commit_msg>Added a bit more spacing between setting print view sections<commit_after>/*
 * == BSD2 LICENSE ==
 * Copyright (c) 2017, Tidepool Project
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the associated License, which is identical to the BSD 2-Clause
 * License as published by the Open Source Initiative at opensource.org.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the License for more details.
 *
 * You should have received a copy of the License along with this program; if
 * not, you can obtain one from Tidepool Project at tidepool.org.
 * == BSD2 LICENSE ==
 */

/* eslint-disable lodash/prefer-lodash-method */

import _ from 'lodash';

import PrintView from './PrintView';

import {
  getDeviceMeta,
  processBasalRateData,
} from '../../utils/settings/data';

import {
  bolusTitle,
} from '../../utils/settings/nonTandemData';

class SettingsPrintView extends PrintView {
  constructor(doc, data, opts) {
    super(doc, data, opts);

    this.manufacturer = _.get(data, 'source', '').toLowerCase();
    this.deviceMeta = getDeviceMeta(data, opts.timePrefs);
    this.layoutColumns = {};
  }

  render() {
    // console.log('doc', this.doc);
    console.log('data', this.data);
    // console.log('deviceMeta', this.deviceMeta);
    this.doc.addPage();
    this.renderDeviceMeta();
    this.renderBasalSchedules();
    this.renderBolusDetails();
  }

  renderDeviceMeta() {
    this.doc
    .font(this.boldFont)
    .fontSize(this.defaultFontSize)
    .text(this.data.source, { continued: true })
    .font(this.font)
    .text(` Uploaded on ${this.deviceMeta.uploaded}`, { continued: true })
    .text(` › Serial Number: ${this.deviceMeta.serial}`)
    .moveDown();

    this.resetText();
    this.doc.moveDown();
  }

  renderBasalSchedules() {
    this.renderSectionHeading('Basal Rates');

    const {
      activeSchedule,
      basalSchedules,
    } = this.data;

    this.setLayoutColumns(this.chartArea.width, 3, 20);

    const tableWidth = this.layoutColumns.itemWidth;

    const tableColumns = [
      {
        id: 'start',
        header: 'Start time',
        align: 'left',
        width: tableWidth - 50,
      },
      {
        id: 'rate',
        header: 'Value',
        align: 'right',
        width: 50,
      },
    ];

    const sortedSchedules = _.sortByOrder(basalSchedules,
      [
        schedule => (schedule.name === activeSchedule ? 1 : 0),
        schedule => schedule.value.length,
        'name',
      ],
      ['desc', 'desc', 'asc']
    );

    _.each(sortedSchedules, (schedule, index) => {

      const activeColumn = index < this.layoutColumns.count
        ? index % this.layoutColumns.count
        : this.getShortestLayoutColumn();

      this.gotoLayoutColumnPosition(activeColumn);

      const note = schedule.name === activeSchedule ? 'Active at Upload' : null;

      const data = processBasalRateData(schedule);

      const heading = {
        text: schedule.name,
        subText: 'U/hr',
        note,
      };

      this.renderTableHeading(heading, {
        columnDefaults: {
          fill: {
            color: this.colors.basal,
            opacity: 0.4,
          },
          width: tableWidth,
        },
      });

      this.updateLayoutColumnPosition(activeColumn);

      this.renderTable(tableColumns, data, {
        columnDefaults: {
          zebra: true,
          headerFill: true,
        },
      });

      this.updateLayoutColumnPosition(activeColumn);
    });

    this.resetText();
  }

  renderBolusDetails() {
    this.doc.x = this.chartArea.leftEdge;
    this.doc.y = this.layoutColumns.columns[this.getLongestLayoutColumn()].y;
    this.doc.moveDown();

    this.renderSectionHeading(bolusTitle(this.manufacturer));

    this.resetText();
    this.doc.moveDown();
  }
}

export default SettingsPrintView;
<|endoftext|>


<commit_before>import {modelToEditor} from "../../editor/node-convert"
import {downloadFile} from "../download"
import {createSlug, getDatabasesIfNeeded} from "../tools"
import JSZip from "jszip"
import JSZipUtils from "jszip-utils"

import {DocxExporterCitations} from "./citations"
import {DocxExporterImages} from "./images"
import {DocxExporterRender} from "./render"
import {DocxExporterRichtext} from "./richtext"
import {DocxExporterXml} from "./xml"
import {DocxExporterRels} from "./rels"
import {DocxExporterFootnotes} from "./footnotes"

/*
Exporter to Microsoft Word.

This exporter is experimental.

TODO:
* equations (inline and figure)
*/

export class DocxExporter {
    constructor(doc, bibDB, imageDB) {
        let that = this
        this.doc = doc
        // We use the doc in the pm format as this is what we will be using
        // throughout the application in the future.
        this.pmDoc = modelToEditor(this.doc)
        this.template = false
        this.zip = false
        this.extraFiles = {}
        this.maxRelId = {}
        this.pmBib = false
        this.docTitle = this.pmDoc.child(0).textContent
        this.footnotes = new DocxExporterFootnotes(this)
        this.render = new DocxExporterRender(this)

        this.xml = new DocxExporterXml(this)

        this.rels = new DocxExporterRels(this, 'document')
        getDatabasesIfNeeded(this, doc, function() {
            that.images = new DocxExporterImages(that, that.imageDB, that.rels, that.pmDoc)
            that.citations = new DocxExporterCitations(that, that.bibDB, that.pmDoc)
            that.richtext = new DocxExporterRichtext(
                that,
                that.rels,
                that.citations,
                that.images
            )
            that.createFile()
        })
    }

    getTemplate() {
        let that = this
        return new window.Promise((resolve) => {
            JSZipUtils.getBinaryContent(
                staticUrl + 'docx/template.docx',
                function(err, template){
                    that.template = template
                    resolve()
                }
            )
        })
    }

    createFile() {
        let that = this
        this.citations.formatCitations()
        this.pmBib = this.citations.pmBib
        this.zip = new JSZip()

        this.getTemplate().then(() => {
                return that.zip.loadAsync(that.template)
            }).then(() => {
                return that.render.init()
            }).then(() => {
                return that.rels.init()
            }).then(() => {
                return that.images.init()
            }).then(() => {
                return that.footnotes.init()
            }).then(() => {
                that.render.getTagData()
                that.render.render()
                that.prepareAndDownload()
            })

    }

    prepareAndDownload() {
        let that = this

        this.xml.allToZip()

        for (let fileName in this.extraFiles) {
            this.zip.file(fileName, this.extraFiles[fileName])
        }
        this.zip.generateAsync({type:"blob"}).then(function(out){
            downloadFile(createSlug(that.docTitle)+'.docx', out)
        })
    }

}
<commit_msg>clenaup whitespace<commit_after>import {modelToEditor} from "../../editor/node-convert"
import {downloadFile} from "../download"
import {createSlug, getDatabasesIfNeeded} from "../tools"
import JSZip from "jszip"
import JSZipUtils from "jszip-utils"

import {DocxExporterCitations} from "./citations"
import {DocxExporterImages} from "./images"
import {DocxExporterRender} from "./render"
import {DocxExporterRichtext} from "./richtext"
import {DocxExporterXml} from "./xml"
import {DocxExporterRels} from "./rels"
import {DocxExporterFootnotes} from "./footnotes"

/*
Exporter to Microsoft Word.

This exporter is experimental.

TODO:
* equations (inline and figure)
*/

export class DocxExporter {
    constructor(doc, bibDB, imageDB) {
        let that = this
        this.doc = doc
        // We use the doc in the pm format as this is what we will be using
        // throughout the application in the future.
        this.pmDoc = modelToEditor(this.doc)
        this.template = false
        this.zip = false
        this.extraFiles = {}
        this.maxRelId = {}
        this.pmBib = false
        this.docTitle = this.pmDoc.child(0).textContent
        this.footnotes = new DocxExporterFootnotes(this)
        this.render = new DocxExporterRender(this)

        this.xml = new DocxExporterXml(this)

        this.rels = new DocxExporterRels(this, 'document')
        getDatabasesIfNeeded(this, doc, function() {
            that.images = new DocxExporterImages(that, that.imageDB, that.rels, that.pmDoc)
            that.citations = new DocxExporterCitations(that, that.bibDB, that.pmDoc)
            that.richtext = new DocxExporterRichtext(
                that,
                that.rels,
                that.citations,
                that.images
            )
            that.createFile()
        })
    }

    getTemplate() {
        let that = this
        return new window.Promise((resolve) => {
            JSZipUtils.getBinaryContent(
                staticUrl + 'docx/template.docx',
                function(err, template){
                    that.template = template
                    resolve()
                }
            )
        })
    }

    createFile() {
        let that = this
        this.citations.formatCitations()
        this.pmBib = this.citations.pmBib
        this.zip = new JSZip()

        this.getTemplate().then(() => {
                return that.zip.loadAsync(that.template)
            }).then(() => {
                return that.render.init()
            }).then(() => {
                return that.rels.init()
            }).then(() => {
                return that.images.init()
            }).then(() => {
                return that.footnotes.init()
            }).then(() => {
                that.render.getTagData()
                that.render.render()
                that.prepareAndDownload()
            })
    }

    prepareAndDownload() {
        let that = this

        this.xml.allToZip()

        for (let fileName in this.extraFiles) {
            this.zip.file(fileName, this.extraFiles[fileName])
        }
        this.zip.generateAsync({type:"blob"}).then(function(out){
            downloadFile(createSlug(that.docTitle)+'.docx', out)
        })
    }

}
<|endoftext|>


<commit_before>/**
 * An Alloy widget to manage windows stack in same code for iOS and Android, with drawer support
 *
 * more https://github.com/HazemKhaled/TiWindowStack
 */
function WindowStack()
{
    // The navigation object for iOS
    var navigationWindow = null,
        // Windows array for history pepuse
        windows = [],
        that = this,
        ANDROID = Ti.Platform.name === 'android',
        // Since iOS 10 Ti.Platform.name return 'iOS' instead of 'iPhone OS'
        // https://jira.appcelerator.org/browse/TIDOC-2611
        IOS = Ti.Platform.name === 'iPhone OS' || Ti.Platform.name === 'iOS';

    /**
     * Who am i?
     *
     * @type {String}
     */
    this.apiName = 'ti-window-stack';

    /**
     * Constant to open the window into the center of the drawer
     *
     * @type {Number}
     */
    this.CENTER_WINDOW = 101;

    /**
     * Constant to open the window into the right of the drawer
     *
     * @type {Number}
     */
    this.RIGHT_WINDOW = 102;

    /**
     * Constant to open the window into the left of the drawer
     *
     * @type {Number}
     */
    this.LEFT_WINDOW = 103;

    /**
     * Which side to open new window in the drawer, center is default
     *
     * @type {[type]}
     */
    this.targetInDrawer = this.CENTER_WINDOW;

    /**
     * Set external created NavigationWindow
     *
     * @param  {Ti.UI.NavigationWindow} _navigationWindow NavigationWindow to set.
     * @return {void}
     */
    this.setNavigationWindow = function(_navigationWindow)
    {
        navigationWindow = _navigationWindow;
    };

    /**
     * Setter of targetInDrawer.
     *
     * @param  {Number} targetInDrawer Constants: WindowStack.CENTER_WINDOW, WindowStack.RIGHT_WINDOW or WindowStack.LEFT_WINDOW
     * @return {void}
     */
    this.setTargetInDrawer = function (targetInDrawer)
    {
        if ([that.CENTER_WINDOW, that.RIGHT_WINDOW, that.LEFT_WINDOW].indexOf(targetInDrawer) !== -1){
            this.targetInDrawer = targetInDrawer;
        }
    };

    /**
     * Open window into the stack, you can pass instance from nl.fokkezb.drawer to open this
     * window into drawer center window.
     *
     * @param  {Ti.UI.Window/Ti.UI.View} _window Window/View to open
     * @param  {nl.fokkezb.drawer} drawer nl.fokkezb.drawer instance
     * @return {void}
     */
    this.open = function(_window, drawer)
    {
        if (IOS) {
            // Create navigationWindow if we don't have, or if we have side menu
            if (navigationWindow === null || drawer) {
                navigationWindow = Ti.UI.iOS.createNavigationWindow({
                    window: _window
                });
                if (drawer) {
                    // Open the window in center, right or left?
                    var openIn = 'setCenterWindow';
                    if (that.targetInDrawer === that.RIGHT_WINDOW) {
                        openIn = 'setRightWindow';
                    } else if (that.targetInDrawer === that.LEFT_WINDOW) {
                        openIn = 'setLeftWindow';
                    }

                    // Open the window into the drawer
                    drawer[openIn](navigationWindow);
                } else {
                    navigationWindow.open();
                }

                // Reset our local stack refrance
                windows = [];
            } else {
                // Add this window to my stack reference
                windows.push(_window);
                // Or just push new window to the stack
                navigationWindow.openWindow(_window);
            }
        } else {
            if (drawer) {
                // Extend some properties to drawer containner
                _.extend(drawer.window, _.pick(_window, ['title', 'keepScreenOn', 'exitOnClose']));
                // Since android center item is view not a window, we have to fire it ourselves
                _window.fireEvent('open');
                // Generate Android menu
                var activity = drawer.window.getActivity();
                activity.onCreateOptionsMenu = function(e) {
                    // Clean up all items from any other controller
                    e.menu.clear();
                    if (_window.hasOwnProperty('onCreateOptionsMenu')) {
                        _window.onCreateOptionsMenu(e);
                    }
                };

                // Try to refresh menus if the activity created
                try {
                    activity.invalidateOptionsMenu();
                } catch (e) {
                    Ti.API.warn('Maybe we still do not have activity to update the menu, it works now by the way');
                }

                // Open the window in center, right or left?
                var openIn = 'setCenterWindow';
                if (that.targetInDrawer === that.RIGHT_WINDOW) {
                    openIn = 'setRightWindow';
                } else if (that.targetInDrawer === that.LEFT_WINDOW) {
                    openIn = 'setLeftWindow';
                }

                // Open the window into the drawer
                drawer[openIn](_window);

                // Reset our local stack refrance
                windows = [];
            } else {
                // Add this window to my stack reference
                windows.push(_window);
                // Open the window
                _window.open();
            }
        }

        // On close the window update the windows array
        _window.addEventListener('close', function() {
            windows = _.without(windows, _window);
        });
    };

    /**
     * Retrieve the size of the current window stack.
     *
     * @return {Number}
     */
    this.size = function()
    {
        return windows.length;
    };

    /**
     * Pop window from the stack.
     *
     * @param  {Ti.UI.Window/Ti.UI.View} _window Window/View to open
     * @return {void}
     */
    this.close = function(_window)
    {
        if (IOS) {
            navigationWindow.closeWindow(_window);
        } else {
            _window.close();
        }
    };

    /**
     * [description]
     *
     * @return {void}
     */
    this.back = function()
    {
        // Get last window in the stack
        var _window = _.last(windows);
        // In case assign this function directly to UI item, this will pass to the UI item it self, better use that
        if (_window) that.close(_window);
    };

    /**
     * By default, closes all the windows, one after the other, starting for the current window.
     * User sees all the windows getting closed.
     *
     * @param  {Array} _params _params.animated: Controls wether to animate when
     *                         all windows are getting closed or not.
     * @return {void}
     */
    this.home = function(_params)
    {
        _params = _params || { animated: true };

        var lastLength = windows.length,
            interval;

        if (_params.animated) {
            // Animate the window closing so the user sees all of them getting
            // closed one after the other
            Alloy.Globals.homeInterval = interval = setInterval(function() {
                if (lastLength === windows.length) {
                    Alloy.Globals.windowStack.back();
                    lastLength--;

                    if (lastLength === 0 ||
                        // Center window is actually view on Android
                        windows[lastLength - 1].apiName === 'Ti.UI.View') {
                        clearInterval(Alloy.Globals.homeInterval);
                    }
                }
            }, 100);
        } else {
            // Closes all other windows except the last one (so users don't see
            // the other windows getting closed) and finally closes the current
            // window.
            var lastWindow = _.last(windows),
                rest = _.without(windows, lastWindow);

            rest.forEach(function(window, index){
                if (index === 0 && window.apiName === 'Ti.UI.View') {
                    return;
                }
                that.close(window);
            });
            that.close(lastWindow);
        }
    };

    /**
     * Close all Windows, close the navigationWindow or drawer.
     *
     * @param  {nl.fokkezb.drawer} drawer Drawer to close. (Optional)
     * @param  {Callable} closeCallBack Will call it after close last screen. (Optional)
     * @return {void}
     */
    this.destroy = function(drawer, closeCallBack)
    {
        if (drawer) {
            if (closeCallBack) {
                if (IOS) {
                    drawer.addEventListener('close', closeCallBack);
                } else {
                    drawer.window.addEventListener('close', closeCallBack);
                }
            }
            drawer.close();
        } else if (IOS) {
            closeCallBack && navigationWindow.addEventListener('close', closeCallBack);
            navigationWindow.close();
        } else {
            closeCallBack && _.last(windows).addEventListener('close', closeCallBack);
            _.each(windows, function(_window) {
                _window.close();
            });
        }
    };
}

/**
 * Create new instance.
 *
 * @return {WindowStack} New instance of WindowStack.
 */
exports.createWindowStack = function()
{
    return new WindowStack();
};
<commit_msg>Supporting optional openWindowParams within open()<commit_after>/**
 * An Alloy widget to manage windows stack in same code for iOS and Android, with drawer support
 *
 * more https://github.com/HazemKhaled/TiWindowStack
 */
function WindowStack()
{
    // The navigation object for iOS
    var navigationWindow = null,
        // Windows array for history pepuse
        windows = [],
        that = this,
        ANDROID = Ti.Platform.name === 'android',
        // Since iOS 10 Ti.Platform.name return 'iOS' instead of 'iPhone OS'
        // https://jira.appcelerator.org/browse/TIDOC-2611
        IOS = Ti.Platform.name === 'iPhone OS' || Ti.Platform.name === 'iOS';

    /**
     * Who am i?
     *
     * @type {String}
     */
    this.apiName = 'ti-window-stack';

    /**
     * Constant to open the window into the center of the drawer
     *
     * @type {Number}
     */
    this.CENTER_WINDOW = 101;

    /**
     * Constant to open the window into the right of the drawer
     *
     * @type {Number}
     */
    this.RIGHT_WINDOW = 102;

    /**
     * Constant to open the window into the left of the drawer
     *
     * @type {Number}
     */
    this.LEFT_WINDOW = 103;

    /**
     * Which side to open new window in the drawer, center is default
     *
     * @type {[type]}
     */
    this.targetInDrawer = this.CENTER_WINDOW;

    /**
     * Set external created NavigationWindow
     *
     * @param  {Ti.UI.NavigationWindow} _navigationWindow NavigationWindow to set.
     * @return {void}
     */
    this.setNavigationWindow = function(_navigationWindow)
    {
        navigationWindow = _navigationWindow;
    };

    /**
     * Setter of targetInDrawer.
     *
     * @param  {Number} targetInDrawer Constants: WindowStack.CENTER_WINDOW, WindowStack.RIGHT_WINDOW or WindowStack.LEFT_WINDOW
     * @return {void}
     */
    this.setTargetInDrawer = function (targetInDrawer)
    {
        if ([that.CENTER_WINDOW, that.RIGHT_WINDOW, that.LEFT_WINDOW].indexOf(targetInDrawer) !== -1){
            this.targetInDrawer = targetInDrawer;
        }
    };

    /**
     * Open window into the stack, you can pass instance from nl.fokkezb.drawer to open this
     * window into drawer center window.
     *
     * @param  {Ti.UI.Window/Ti.UI.View} _window Window/View to open.
     * @param  {nl.fokkezb.drawer} drawer nl.fokkezb.drawer instance. (Optional)
     * @param  {openWindowParams} params Animation or display properties to use when opening the window. (Optional)
     * @return {void}
     */
    this.open = function(_window, drawer, params)
    {
        drawer = drawer || false;
        params = params || {};

        if (IOS) {
            // Create navigationWindow if we don't have, or if we have side menu
            if (navigationWindow === null || drawer) {
                navigationWindow = Ti.UI.iOS.createNavigationWindow({
                    window: _window
                });
                if (drawer) {
                    // Open the window in center, right or left?
                    var openIn = 'setCenterWindow';
                    if (that.targetInDrawer === that.RIGHT_WINDOW) {
                        openIn = 'setRightWindow';
                    } else if (that.targetInDrawer === that.LEFT_WINDOW) {
                        openIn = 'setLeftWindow';
                    }

                    // Open the window into the drawer
                    drawer[openIn](navigationWindow);
                } else {
                    navigationWindow.open(params);
                }

                // Reset our local stack refrance
                windows = [];
            } else {
                // Add this window to my stack reference
                windows.push(_window);
                // Or just push new window to the stack
                navigationWindow.openWindow(_window);
            }
        } else {
            if (drawer) {
                // Extend some properties to drawer containner
                _.extend(drawer.window, _.pick(_window, ['title', 'keepScreenOn', 'exitOnClose']));
                // Since android center item is view not a window, we have to fire it ourselves
                _window.fireEvent('open');
                // Generate Android menu
                var activity = drawer.window.getActivity();
                activity.onCreateOptionsMenu = function(e) {
                    // Clean up all items from any other controller
                    e.menu.clear();
                    if (_window.hasOwnProperty('onCreateOptionsMenu')) {
                        _window.onCreateOptionsMenu(e);
                    }
                };

                // Try to refresh menus if the activity created
                try {
                    activity.invalidateOptionsMenu();
                } catch (e) {
                    Ti.API.warn('Maybe we still do not have activity to update the menu, it works now by the way');
                }

                // Open the window in center, right or left?
                var openIn = 'setCenterWindow';
                if (that.targetInDrawer === that.RIGHT_WINDOW) {
                    openIn = 'setRightWindow';
                } else if (that.targetInDrawer === that.LEFT_WINDOW) {
                    openIn = 'setLeftWindow';
                }

                // Open the window into the drawer
                drawer[openIn](_window);

                // Reset our local stack refrance
                windows = [];
            } else {
                // Add this window to my stack reference
                windows.push(_window);
                // Open the window
                _window.open(params);
            }
        }

        // On close the window update the windows array
        _window.addEventListener('close', function() {
            windows = _.without(windows, _window);
        });
    };

    /**
     * Retrieve the size of the current window stack.
     *
     * @return {Number}
     */
    this.size = function()
    {
        return windows.length;
    };

    /**
     * Pop window from the stack.
     *
     * @param  {Ti.UI.Window/Ti.UI.View} _window Window/View to open
     * @return {void}
     */
    this.close = function(_window)
    {
        if (IOS) {
            navigationWindow.closeWindow(_window);
        } else {
            _window.close();
        }
    };

    /**
     * [description]
     *
     * @return {void}
     */
    this.back = function()
    {
        // Get last window in the stack
        var _window = _.last(windows);
        // In case assign this function directly to UI item, this will pass to the UI item it self, better use that
        if (_window) that.close(_window);
    };

    /**
     * By default, closes all the windows, one after the other, starting for the current window.
     * User sees all the windows getting closed.
     *
     * @param  {Array} _params _params.animated: Controls wether to animate when
     *                         all windows are getting closed or not.
     * @return {void}
     */
    this.home = function(_params)
    {
        _params = _params || { animated: true };

        var lastLength = windows.length,
            interval;

        if (_params.animated) {
            // Animate the window closing so the user sees all of them getting
            // closed one after the other
            Alloy.Globals.homeInterval = interval = setInterval(function() {
                if (lastLength === windows.length) {
                    Alloy.Globals.windowStack.back();
                    lastLength--;

                    if (lastLength === 0 ||
                        // Center window is actually view on Android
                        windows[lastLength - 1].apiName === 'Ti.UI.View') {
                        clearInterval(Alloy.Globals.homeInterval);
                    }
                }
            }, 100);
        } else {
            // Closes all other windows except the last one (so users don't see
            // the other windows getting closed) and finally closes the current
            // window.
            var lastWindow = _.last(windows),
                rest = _.without(windows, lastWindow);

            rest.forEach(function(window, index){
                if (index === 0 && window.apiName === 'Ti.UI.View') {
                    return;
                }
                that.close(window);
            });
            that.close(lastWindow);
        }
    };

    /**
     * Close all Windows, close the navigationWindow or drawer.
     *
     * @param  {nl.fokkezb.drawer} drawer Drawer to close. (Optional)
     * @param  {Callable} closeCallBack Will call it after close last screen. (Optional)
     * @return {void}
     */
    this.destroy = function(drawer, closeCallBack)
    {
        if (drawer) {
            if (closeCallBack) {
                if (IOS) {
                    drawer.addEventListener('close', closeCallBack);
                } else {
                    drawer.window.addEventListener('close', closeCallBack);
                }
            }
            drawer.close();
        } else if (IOS) {
            closeCallBack && navigationWindow.addEventListener('close', closeCallBack);
            navigationWindow.close();
        } else {
            closeCallBack && _.last(windows).addEventListener('close', closeCallBack);
            _.each(windows, function(_window) {
                _window.close();
            });
        }
    };
}

/**
 * Create new instance.
 *
 * @return {WindowStack} New instance of WindowStack.
 */
exports.createWindowStack = function()
{
    return new WindowStack();
};
<|endoftext|>


<commit_before>// FIXME: these should be shared utils for Array testing

function printDesc(obj, key) {
    var pd = Object.getOwnPropertyDescriptor(obj, key);
    print(JSON.stringify(pd));  // FIXME
}

function dumpValue(v) {
    var i, n;
    var res = '';

    if (v === undefined) {
        return 'undefined';
    } else if (v === null) {
        return 'null';
    }

    n = v.length;
    res = (typeof v) + ' ' + (typeof n) + ' ' + n;
    if (typeof n === 'undefined') {
        return res;
    }

    n = Math.floor(n);
    tmp = [];
    for (i = 0; i < n; i++) {
        if (!v.hasOwnProperty(i)) {
            tmp.push('nonexistent');
        } else {
            x = v[i];
            tmp.push(typeof x + ':' + x);
        }
    }
    res = res + (tmp.length > 0 ? ' ' + tmp.join(',') : '');
    return res;
}

function test(this_value, args) {
    var t;
    var pre, post;

    pre = dumpValue(this_value);
    try {
        t = Array.prototype.push.apply(this_value, args);
    } catch (e) {
        t = e.name;
    }
    post = dumpValue(this_value);
    print(pre, '-->', typeof t, t, '-->', post);
}

/*===
basic
object number 2 number:1,number:2 --> number 2 --> object number 2 number:1,number:2
object number 2 number:1,number:2 --> number 3 --> object number 3 number:1,number:2,number:3
object number 2 number:1,number:2 --> number 5 --> object number 5 number:1,number:2,number:3,number:4,number:5
object number 2 number:1,number:2 --> number 4 --> object number 4 number:1,number:2,object:3,4,object:5,6
object string 5 nonexistent,nonexistent,nonexistent,nonexistent,nonexistent --> number 5 --> object number 5 nonexistent,nonexistent,nonexistent,nonexistent,nonexistent
4294967295
4294967300
9
3
length getter
length getter
length setter 8
length getter
object number 3 nonexistent,nonexistent,nonexistent --> number 8 --> object number 8 nonexistent,nonexistent,nonexistent,number:1,number:2,number:3,number:4,number:5
8
object number 1 string:foo --> number 6 --> object number 6 string:foo,number:1,number:2,string:baz,number:4,number:5
object number 1 string:foo --> number 2 --> object number 2 string:foo,string:bar
3 setter 3
3 getter
object number 1 string:foo --> number 6 --> object number 6 string:foo,number:1,number:2,string:setter-3,number:4,number:5
{"value":"foo","writable":true,"enumerable":true,"configurable":true}
{"value":1,"writable":true,"enumerable":true,"configurable":true}
{"value":2,"writable":true,"enumerable":false,"configurable":false}
{"enumerable":false,"configurable":false}
{"value":4,"writable":true,"enumerable":true,"configurable":true}
===*/

print('basic');

function basicTest() {
    // simple cases

    test([1,2], []);
    test([1,2], [3]);
    test([1,2], [3,4,5]);

    // arrays are not flattened like in concat

    test([1,2], [[3,4],[5,6]]);

    // zero length push(), still updates length, i.e. converts from
    // string to number here

    t = { length: '5' };
    test(t, []);

    // if the final length is outside 32-bit range, it will NOT wrap
    // because it's not coerced with ToUint32() again; it will wrap
    // on the next call!  (Not using test() here to avoid insane
    // debug dump :)

    t = { length: 256*256*256*256 - 1 };  // max array length
    print(t.length);
    Array.prototype.push.call(t, 1, 2, 3, 4, 5);
    print(t.length);  // -> 256*256*256*256 + 4
    Array.prototype.push.call(t, 1, 2, 3, 4, 5);
    print(t.length);  // -> 4 + 5 = 9

    // length side effect - only written once at the end
    // note that test() debug printing interferes with this

    t = {
        mylen: 3,

        get length() { print('length getter'); return this.mylen; },
        set length(n) { print('length setter', n); this.mylen = n; }
    };
    print(t.mylen);
    //Array.prototype.push.call(t, 1, 2, 3, 4, 5);
    test(t, [1, 2, 3, 4, 5]);
    print(t.mylen);

    // if multiple elements are inserted and a [[Put]] fails in the middle,
    // some elements may be inserted but 'length' is not updated at all.
    // e.g. here '1' and '2' will be written, [[Put]] to '3' fails, and
    // 'length' should remain 1.
    // (V8 3.7.12.22 will just skip the failed [[Put]])

    t = Object.create(Object.prototype, {
        '0': { value: 'foo', writable: true, enumerable: true, configurable: true },
        // '1' is empty
        // '2' is empty
        '3': { value: 'baz', writable: false, enumerable: false, configurable: false },
        'length': { value: 1, writable: true, enumerable: true, configurable: true }
    });
    test(t, [1, 2, 3, 4, 5]);

    // Since [[Put]] is used, an attempt to write to a non-writable property is always
    // a TypeError, even if SameValue(oldVal, newVal) is true.  This is the case
    // because [[Put]] first calls [[CanPut]] which refuses an attempt to write
    // to a non-writable data property.

     t = Object.create(Object.prototype, {
        '0': { value: 'foo', writable: true, enumerable: true, configurable: true },
        '1': { value: 'bar', writable: false, enumerable: false, configurable: false },
        'length': { value: 1, writable: true, enumerable: true, configurable: true }
    });
    test(t, ['bar']);

    // Since [[Put]] is used, existing elements keep their attributes while new
    // elements get default [[Put]] attributes (writable, enumerable, configurable),
    // and setters get called.

    t = Object.create(Object.prototype, {
        '0': { value: 'foo', writable: true, enumerable: true, configurable: true },
        // '1' is empty (new element)
        '2': { value: 'quux', writable: true, enumerable: false, configurable: false },  // attributes kept
        '3': { get: function() { print('3 getter'); return this.my3; },
               set: function(v) { print('3 setter', v); this.my3 = 'setter-' + v; } },
        'length': { value: 1, writable: true, enumerable: true, configurable: true }
    });
    t.my3 = 'baz';
    test(t, [1, 2, 3, 4, 5]);
    printDesc(t, '0');
    printDesc(t, '1');
    printDesc(t, '2');
    printDesc(t, '3');
    printDesc(t, '4');
}

try {
    basicTest();
} catch (e) {
    print(e);
}

/*===
coercion
undefined --> string TypeError --> undefined
null --> string TypeError --> null
boolean undefined undefined --> number 0 --> boolean undefined undefined
boolean undefined undefined --> number 0 --> boolean undefined undefined
number undefined undefined --> number 0 --> number undefined undefined
string number 3 string:f,string:o,string:o --> number 3 --> string number 3 string:f,string:o,string:o
object number 2 number:1,number:2 --> number 2 --> object number 2 number:1,number:2
object undefined undefined --> number 0 --> object number 0
object number 2 string:foo,string:bar --> number 2 --> object number 2 string:foo,string:bar
===*/

print('coercion');

function coercionTest() {
    test(undefined);
    test(null);
    test(true);
    test(false);
    test(123);
    test('foo');
    test([1,2]);
    test({ foo: 1, bar: 2 });
    test({ '0': 'foo', '1': 'bar', length: 2 });

    // coercion: ToObject(this), [[Get]] "length", ToUint32(length)
}

try {
    coercionTest();
} catch (e) {
    print(e);
}

/*===
non-extensible
object number 3 number:1,number:2,number:3 --> number 4 --> object number 4 number:1,number:2,number:3,nonexistent
object number 3 string:foo,string:bar,string:quux --> number 4 --> object number 4 string:foo,string:bar,string:quux,nonexistent
===*/

print('non-extensible');

function nonExtensibleTest() {
    var t;

    // non-extensible array

    t = [1,2,3];
    Object.preventExtensions(t);
    test(t, [ 4 ]);

    // non-extensible object

    t = { '0': 'foo', '1': 'bar', '2': 'quux', length: 3 };
    Object.preventExtensions(t);
    test(t, [ 1 ]);
}

try {
    nonExtensibleTest();
} catch (e) {
    print(e);
}

<commit_msg>fix testcase for Array.prototype.push<commit_after>// FIXME: these should be shared utils for Array testing

function printDesc(obj, key) {
    var pd = Object.getOwnPropertyDescriptor(obj, key);
    print(JSON.stringify(pd));  // FIXME
}

function dumpValue(v) {
    var i, n;
    var res = '';

    if (v === undefined) {
        return 'undefined';
    } else if (v === null) {
        return 'null';
    }

    n = v.length;
    res = (typeof v) + ' ' + (typeof n) + ' ' + n;
    if (typeof n === 'undefined') {
        return res;
    }

    n = Math.floor(n);
    tmp = [];
    for (i = 0; i < n; i++) {
        if (!v.hasOwnProperty(i)) {
            tmp.push('nonexistent');
        } else {
            x = v[i];
            tmp.push(typeof x + ':' + x);
        }
    }
    res = res + (tmp.length > 0 ? ' ' + tmp.join(',') : '');
    return res;
}

function test(this_value, args) {
    var t;
    var pre, post, res;

    pre = dumpValue(this_value);
    try {
        t = Array.prototype.push.apply(this_value, args);
        res = typeof t + " " + t;
    } catch (e) {
        res = e.name;
    }
    post = dumpValue(this_value);
    print(pre, '-->', res, '-->', post);
}

/*===
basic
object number 2 number:1,number:2 --> number 2 --> object number 2 number:1,number:2
object number 2 number:1,number:2 --> number 3 --> object number 3 number:1,number:2,number:3
object number 2 number:1,number:2 --> number 5 --> object number 5 number:1,number:2,number:3,number:4,number:5
object number 2 number:1,number:2 --> number 4 --> object number 4 number:1,number:2,object:3,4,object:5,6
object string 5 nonexistent,nonexistent,nonexistent,nonexistent,nonexistent --> number 5 --> object number 5 nonexistent,nonexistent,nonexistent,nonexistent,nonexistent
4294967295
4294967300
9
3
length getter
length getter
length setter 8
length getter
object number 3 nonexistent,nonexistent,nonexistent --> number 8 --> object number 8 nonexistent,nonexistent,nonexistent,number:1,number:2,number:3,number:4,number:5
8
object number 1 string:foo --> TypeError --> object number 1 string:foo
object number 1 string:foo --> TypeError --> object number 1 string:foo
3 setter 3
3 getter
object number 1 string:foo --> number 6 --> object number 6 string:foo,number:1,number:2,string:setter-3,number:4,number:5
{"value":"foo","writable":true,"enumerable":true,"configurable":true}
{"value":1,"writable":true,"enumerable":true,"configurable":true}
{"value":2,"writable":true,"enumerable":false,"configurable":false}
{"enumerable":false,"configurable":false}
{"value":4,"writable":true,"enumerable":true,"configurable":true}
===*/

print('basic');

function basicTest() {
    // simple cases

    test([1,2], []);
    test([1,2], [3]);
    test([1,2], [3,4,5]);

    // arrays are not flattened like in concat

    test([1,2], [[3,4],[5,6]]);

    // zero length push(), still updates length, i.e. converts from
    // string to number here

    t = { length: '5' };
    test(t, []);

    // if the final length is outside 32-bit range, it will NOT wrap
    // because it's not coerced with ToUint32() again; it will wrap
    // on the next call!  (Not using test() here to avoid insane
    // debug dump :)

    t = { length: 256*256*256*256 - 1 };  // max array length
    print(t.length);
    Array.prototype.push.call(t, 1, 2, 3, 4, 5);
    print(t.length);  // -> 256*256*256*256 + 4
    Array.prototype.push.call(t, 1, 2, 3, 4, 5);
    print(t.length);  // -> 4 + 5 = 9

    // length side effect - only written once at the end
    // note that test() debug printing interferes with this

    t = {
        mylen: 3,

        get length() { print('length getter'); return this.mylen; },
        set length(n) { print('length setter', n); this.mylen = n; }
    };
    print(t.mylen);
    //Array.prototype.push.call(t, 1, 2, 3, 4, 5);
    test(t, [1, 2, 3, 4, 5]);
    print(t.mylen);

    // if multiple elements are inserted and a [[Put]] fails in the middle,
    // some elements may be inserted but 'length' is not updated at all.
    // e.g. here '1' and '2' will be written, [[Put]] to '3' fails, and
    // 'length' should remain 1.
    // (V8 3.7.12.22 will just skip the failed [[Put]])

    t = Object.create(Object.prototype, {
        '0': { value: 'foo', writable: true, enumerable: true, configurable: true },
        // '1' is empty
        // '2' is empty
        '3': { value: 'baz', writable: false, enumerable: false, configurable: false },
        'length': { value: 1, writable: true, enumerable: true, configurable: true }
    });
    test(t, [1, 2, 3, 4, 5]);

    // Since [[Put]] is used, an attempt to write to a non-writable property is always
    // a TypeError, even if SameValue(oldVal, newVal) is true.  This is the case
    // because [[Put]] first calls [[CanPut]] which refuses an attempt to write
    // to a non-writable data property.

     t = Object.create(Object.prototype, {
        '0': { value: 'foo', writable: true, enumerable: true, configurable: true },
        '1': { value: 'bar', writable: false, enumerable: false, configurable: false },
        'length': { value: 1, writable: true, enumerable: true, configurable: true }
    });
    test(t, ['bar']);

    // Since [[Put]] is used, existing elements keep their attributes while new
    // elements get default [[Put]] attributes (writable, enumerable, configurable),
    // and setters get called.

    t = Object.create(Object.prototype, {
        '0': { value: 'foo', writable: true, enumerable: true, configurable: true },
        // '1' is empty (new element)
        '2': { value: 'quux', writable: true, enumerable: false, configurable: false },  // attributes kept
        '3': { get: function() { print('3 getter'); return this.my3; },
               set: function(v) { print('3 setter', v); this.my3 = 'setter-' + v; } },
        'length': { value: 1, writable: true, enumerable: true, configurable: true }
    });
    t.my3 = 'baz';
    test(t, [1, 2, 3, 4, 5]);
    printDesc(t, '0');
    printDesc(t, '1');
    printDesc(t, '2');
    printDesc(t, '3');
    printDesc(t, '4');
}

try {
    basicTest();
} catch (e) {
    print(e);
}

/*===
coercion
undefined --> TypeError --> undefined
null --> TypeError --> null
boolean undefined undefined --> number 0 --> boolean undefined undefined
boolean undefined undefined --> number 0 --> boolean undefined undefined
number undefined undefined --> number 0 --> number undefined undefined
string number 3 string:f,string:o,string:o --> TypeError --> string number 3 string:f,string:o,string:o
object number 2 number:1,number:2 --> number 2 --> object number 2 number:1,number:2
object undefined undefined --> number 0 --> object number 0
object number 2 string:foo,string:bar --> number 2 --> object number 2 string:foo,string:bar
===*/

print('coercion');

function coercionTest() {
    test(undefined);
    test(null);
    test(true);
    test(false);
    test(123);
    test('foo');
    test([1,2]);
    test({ foo: 1, bar: 2 });
    test({ '0': 'foo', '1': 'bar', length: 2 });

    // coercion: ToObject(this), [[Get]] "length", ToUint32(length)
}

try {
    coercionTest();
} catch (e) {
    print(e);
}

/*===
non-extensible
object number 3 number:1,number:2,number:3 --> TypeError --> object number 3 number:1,number:2,number:3
object number 3 string:foo,string:bar,string:quux --> TypeError --> object number 3 string:foo,string:bar,string:quux
===*/

print('non-extensible');

function nonExtensibleTest() {
    var t;

    // non-extensible array

    t = [1,2,3];
    Object.preventExtensions(t);
    test(t, [ 4 ]);

    // non-extensible object

    t = { '0': 'foo', '1': 'bar', '2': 'quux', length: 3 };
    Object.preventExtensions(t);
    test(t, [ 1 ]);
}

try {
    nonExtensibleTest();
} catch (e) {
    print(e);
}

<|endoftext|>