<commit_before>import picoModal from 'picomodal';
import parseGPX from './gpx';
import Image from './image';

const AVAILABLE_THEMES = [
    'CartoDB.DarkMatter',
    'CartoDB.DarkMatterNoLabels',
    'CartoDB.Positron',
    'CartoDB.PositronNoLabels',
    'Esri.WorldImagery',
    'OpenStreetMap.Mapnik',
    'OpenStreetMap.BlackAndWhite',
    'OpenTopoMap',
    'Stamen.Terrain',
    'Stamen.TerrainBackground',
    'Stamen.Toner',
    'Stamen.TonerLite',
    'Stamen.TonerBackground',
    'Stamen.Watercolor',
];

const MODAL_CONTENT = {
    help: `

dérive

Drag and drop one or more GPX or JPG files here

If you use Strava, you can obtain a ZIP file of your activity data
in GPX format on your profile
page and clicking "Download all your activities."

All processing happens in your browser. Your files will not be uploaded or
stored anywhere.

In a dérive one or more persons during a certain period drop their
relations, their work and leisure activities, and all their other
usual motives for movement and action, and let themselves be drawn by
the attractions of the terrain and the encounters they find there.

1

Code is available on GitHub.

`,

    exportImage: `

Export Image

<form id="export-settings">

        Format:
        <select name="format">
            <option selected value="png">PNG</option>
            <option value="svg">SVG (no background map)</option>
        </select>

        <input id="render-export" type="button" value="Render">

</form>

`
};

// Adapted from: http://www.html5rocks.com/en/tutorials/file/dndfiles/
function handleFileSelect(map, evt) {
    evt.stopPropagation();
    evt.preventDefault();

    let tracks = [];
    let files = Array.from(evt.dataTransfer.files);
    let modal = buildUploadModal(files.length);

    modal.show();

    let parseGpx = file => new Promise(resolve => {
        parseGPX(file)
            .then(parsedTracks => {
                parsedTracks.forEach(track => {
                    track.filename = file.name;
                    tracks.push(track);
                    map.addTrack(track);
                    modal.addSuccess();
                });
            })
            .then(resolve);
    });

    let handleGpx = file => new Promise(resolve => {
        let reader = new FileReader();
        reader.onload = () => {
            parseGpx(reader.result);
            resolve();
        };
        reader.readAsText(file, 'UTF-8');
    });

    let handleImage = file => new Promise(resolve => {
        let image = new Image(file);

        image.extractExifData().then((image) => {
            if (!image) {
                modal.addFailure({name: file.name, error: 'No geolocation data'});
                return resolve();
            }

            map.addImage(image);
            modal.addSuccess();
            resolve();
        });
    });

    let detectFileType = file => new Promise(resolve => {
        let extension = file.name.split('.').pop();

        switch (extension) {
            case 'gpx':
                handleGpx(file)
                    .then(resolve);
                break;
            case 'jpg':
                handleImage(file)
                    .then(resolve);
                break;
            default:
                console.log(`File ${file.name} is an Unsupported format.`);
                modal.addFailure({name: file.name, error: 'Unsupported file format'});
                resolve();
        }
    });

    let loadFile = file => detectFileType(file)
        .catch(err => {
            modal.addFailure({name: file.name, error: err});
        });

    Promise.all(files.map(loadFile)).then(() => {
        map.recenter();

        modal.finished();
    });
}

function handleDragOver(evt) {
    evt.dataTransfer.dropEffect = 'copy';
    evt.stopPropagation();
    evt.preventDefault();
}

function buildUploadModal(numFiles) {
    let numLoaded = 0;
    let failures = [];
    let getModalContent = () => `

Reading files...

${numLoaded} loaded${
            failures.length ? `, <span class='failures'>${failures.length} failed</span>` : ``
        } of ${numFiles}
`;

    let modal = picoModal({
        content: getModalContent(),
        escCloses: false,
        overlayClose: false,
        overlayStyles: styles => {
            styles.opacity = 0.1;
        },
    });

    modal.afterCreate(() => {
        // Do not allow the modal to be closed before loading is complete.
        // PicoModal does not allow for native toggling
        modal.closeElem().style.display = 'none';
    });

    modal.afterClose(() => modal.destroy());

    // Override the content of the modal, without removing the close button.
    // PicoModal does not allow for native content overwriting.
    modal.setContent = body => {
        Array.from(modal.modalElem().childNodes).forEach(child => {
            if (child !== modal.closeElem()) {
                modal.modalElem().removeChild(child);
            }
        });

        modal.modalElem().insertAdjacentHTML('afterbegin', body);
    };

    modal.addFailure = failure => {
        failures.push(failure);
        modal.setContent(getModalContent());
    };

    modal.addSuccess = () => {
        numLoaded++;
        modal.setContent(getModalContent());
    };

    // Show any errors, or close modal if no errors occurred
    modal.finished = () => {
        if (failures.length === 0) {
            return modal.close();
        }

        let failedItems = failures.map(failure => `<li>${failure.name}</li>`);
        modal.setContent(`

Files loaded

                Loaded ${numLoaded},

                    ${failures.length} failure${failures.length === 1 ? '' : 's'}:

${failedItems.join('')}
`);
        // enable all the methods of closing the window
        modal.closeElem().style.display = '';
        modal.options({
            escCloses: true,
            overlayClose: true,
        });
    };

    return modal;
}

export function buildSettingsModal(tracks, opts, finishCallback) {
    let overrideExisting = opts.lineOptions.overrideExisting ? 'checked' : '';

    if (tracks.length > 0) {
        let allSameColor = tracks.every(trk => {
            return trk.options.color === tracks[0].options.color;
        });

        if (!allSameColor) {
            overrideExisting = false;
        } else {
            opts.lineOptions.color = tracks[0].options.color;
        }
    }

    let detect = opts.lineOptions.detectColors ? 'checked' : '';
    let themes = AVAILABLE_THEMES.map(t => {
        let selected = (t === opts.theme) ? 'selected' : '';

        return `<option ${selected} value="${t}">${t}</option>`;
    });

    let modalContent = `

Options

<form id="settings">

        Theme
        <select name="theme">
            ${themes}
        </select>

        Line color
        <input name="color" type="color" value=${opts.lineOptions.color}>

        Line opacity
        <input name="opacity" type="range" min=0 max=1 step=0.01
            value=${opts.lineOptions.opacity}>

        Line width
        <input name="weight" type="number" min=1 max=100
            value=${opts.lineOptions.weight}>

        Override existing tracks
        <input name="overrideExisting" type="checkbox" ${overrideExisting}>

        Detect color from Strava bulk export
        <input name="detectColors" type="checkbox" ${detect}>

</form>`;

    let modal = picoModal({
        content: modalContent,
        closeButton: true,
        escCloses: true,
        overlayClose: true,
        overlayStyles: (styles) => {
            styles.opacity = 0.1;
        },
    });

    modal.afterClose((modal) => {
        let elements = document.getElementById('settings').elements;
        let options = Object.assign({}, opts);

        for (let opt of ['theme']) {
            options[opt] = elements[opt].value;
        }

        for (let opt of ['color', 'weight', 'opacity']) {
            options.lineOptions[opt] = elements[opt].value;
        }

        for (let opt of ['overrideExisting', 'detectColors']) {
            options.lineOptions[opt] = elements[opt].checked;
        }

        finishCallback(options);
        modal.destroy();
    });

    return modal;
}

export function showModal(type) {
    let modal = picoModal({
        content: MODAL_CONTENT[type],
        overlayStyles: (styles) => {
            styles.opacity = 0.01;
        },
    });

    modal.show();

    return modal;
}

export function initialize(map) {
    let modal = showModal('help');

    window.addEventListener('dragover', handleDragOver, false);

    window.addEventListener('drop', e => {
        if (!modal.destroyed) {
            modal.destroy();
            modal.destroyed = true;
        }
        handleFileSelect(map, e);
    }, false);
}
<commit_msg>Capitalization<commit_after>import picoModal from 'picomodal';
import parseGPX from './gpx';
import Image from './image';

const AVAILABLE_THEMES = [
    'CartoDB.DarkMatter',
    'CartoDB.DarkMatterNoLabels',
    'CartoDB.Positron',
    'CartoDB.PositronNoLabels',
    'Esri.WorldImagery',
    'OpenStreetMap.Mapnik',
    'OpenStreetMap.BlackAndWhite',
    'OpenTopoMap',
    'Stamen.Terrain',
    'Stamen.TerrainBackground',
    'Stamen.Toner',
    'Stamen.TonerLite',
    'Stamen.TonerBackground',
    'Stamen.Watercolor',
];

const MODAL_CONTENT = {
    help: `

dérive

Drag and drop one or more GPX or JPG files here

If you use Strava, you can obtain a ZIP file of your activity data
in GPX format on your profile
page and clicking "Download all your activities."

All processing happens in your browser. Your files will not be uploaded or
stored anywhere.

In a dérive one or more persons during a certain period drop their
relations, their work and leisure activities, and all their other
usual motives for movement and action, and let themselves be drawn by
the attractions of the terrain and the encounters they find there.

1

Code is available on GitHub.

`,

    exportImage: `

Export Image

<form id="export-settings">

        Format:
        <select name="format">
            <option selected value="png">PNG</option>
            <option value="svg">SVG (no background map)</option>
        </select>

        <input id="render-export" type="button" value="Render">

</form>

`
};

// Adapted from: http://www.html5rocks.com/en/tutorials/file/dndfiles/
function handleFileSelect(map, evt) {
    evt.stopPropagation();
    evt.preventDefault();

    let tracks = [];
    let files = Array.from(evt.dataTransfer.files);
    let modal = buildUploadModal(files.length);

    modal.show();

    let parseGpx = file => new Promise(resolve => {
        parseGPX(file)
            .then(parsedTracks => {
                parsedTracks.forEach(track => {
                    track.filename = file.name;
                    tracks.push(track);
                    map.addTrack(track);
                    modal.addSuccess();
                });
            })
            .then(resolve);
    });

    let handleGpx = file => new Promise(resolve => {
        let reader = new FileReader();
        reader.onload = () => {
            parseGpx(reader.result);
            resolve();
        };
        reader.readAsText(file, 'UTF-8');
    });

    let handleImage = file => new Promise(resolve => {
        let image = new Image(file);

        image.extractExifData().then((image) => {
            if (!image) {
                modal.addFailure({name: file.name, error: 'No geolocation data'});
                return resolve();
            }

            map.addImage(image);
            modal.addSuccess();
            resolve();
        });
    });

    let detectFileType = file => new Promise(resolve => {
        let extension = file.name.split('.').pop();

        switch (extension) {
            case 'gpx':
                handleGpx(file)
                    .then(resolve);
                break;
            case 'jpg':
                handleImage(file)
                    .then(resolve);
                break;
            default:
                console.log(`File ${file.name} is an unsupported format.`);
                modal.addFailure({name: file.name, error: 'Unsupported file format'});
                resolve();
        }
    });

    let loadFile = file => detectFileType(file)
        .catch(err => {
            modal.addFailure({name: file.name, error: err});
        });

    Promise.all(files.map(loadFile)).then(() => {
        map.recenter();

        modal.finished();
    });
}

function handleDragOver(evt) {
    evt.dataTransfer.dropEffect = 'copy';
    evt.stopPropagation();
    evt.preventDefault();
}

function buildUploadModal(numFiles) {
    let numLoaded = 0;
    let failures = [];
    let getModalContent = () => `

Reading files...

${numLoaded} loaded${
            failures.length ? `, <span class='failures'>${failures.length} failed</span>` : ``
        } of ${numFiles}
`;

    let modal = picoModal({
        content: getModalContent(),
        escCloses: false,
        overlayClose: false,
        overlayStyles: styles => {
            styles.opacity = 0.1;
        },
    });

    modal.afterCreate(() => {
        // Do not allow the modal to be closed before loading is complete.
        // PicoModal does not allow for native toggling
        modal.closeElem().style.display = 'none';
    });

    modal.afterClose(() => modal.destroy());

    // Override the content of the modal, without removing the close button.
    // PicoModal does not allow for native content overwriting.
    modal.setContent = body => {
        Array.from(modal.modalElem().childNodes).forEach(child => {
            if (child !== modal.closeElem()) {
                modal.modalElem().removeChild(child);
            }
        });

        modal.modalElem().insertAdjacentHTML('afterbegin', body);
    };

    modal.addFailure = failure => {
        failures.push(failure);
        modal.setContent(getModalContent());
    };

    modal.addSuccess = () => {
        numLoaded++;
        modal.setContent(getModalContent());
    };

    // Show any errors, or close modal if no errors occurred
    modal.finished = () => {
        if (failures.length === 0) {
            return modal.close();
        }

        let failedItems = failures.map(failure => `<li>${failure.name}</li>`);
        modal.setContent(`

Files loaded

                Loaded ${numLoaded},

                    ${failures.length} failure${failures.length === 1 ? '' : 's'}:

${failedItems.join('')}
`);
        // enable all the methods of closing the window
        modal.closeElem().style.display = '';
        modal.options({
            escCloses: true,
            overlayClose: true,
        });
    };

    return modal;
}

export function buildSettingsModal(tracks, opts, finishCallback) {
    let overrideExisting = opts.lineOptions.overrideExisting ? 'checked' : '';

    if (tracks.length > 0) {
        let allSameColor = tracks.every(trk => {
            return trk.options.color === tracks[0].options.color;
        });

        if (!allSameColor) {
            overrideExisting = false;
        } else {
            opts.lineOptions.color = tracks[0].options.color;
        }
    }

    let detect = opts.lineOptions.detectColors ? 'checked' : '';
    let themes = AVAILABLE_THEMES.map(t => {
        let selected = (t === opts.theme) ? 'selected' : '';

        return `<option ${selected} value="${t}">${t}</option>`;
    });

    let modalContent = `

Options

<form id="settings">

        Theme
        <select name="theme">
            ${themes}
        </select>

        Line color
        <input name="color" type="color" value=${opts.lineOptions.color}>

        Line opacity
        <input name="opacity" type="range" min=0 max=1 step=0.01
            value=${opts.lineOptions.opacity}>

        Line width
        <input name="weight" type="number" min=1 max=100
            value=${opts.lineOptions.weight}>

        Override existing tracks
        <input name="overrideExisting" type="checkbox" ${overrideExisting}>

        Detect color from Strava bulk export
        <input name="detectColors" type="checkbox" ${detect}>

</form>`;

    let modal = picoModal({
        content: modalContent,
        closeButton: true,
        escCloses: true,
        overlayClose: true,
        overlayStyles: (styles) => {
            styles.opacity = 0.1;
        },
    });

    modal.afterClose((modal) => {
        let elements = document.getElementById('settings').elements;
        let options = Object.assign({}, opts);

        for (let opt of ['theme']) {
            options[opt] = elements[opt].value;
        }

        for (let opt of ['color', 'weight', 'opacity']) {
            options.lineOptions[opt] = elements[opt].value;
        }

        for (let opt of ['overrideExisting', 'detectColors']) {
            options.lineOptions[opt] = elements[opt].checked;
        }

        finishCallback(options);
        modal.destroy();
    });

    return modal;
}

export function showModal(type) {
    let modal = picoModal({
        content: MODAL_CONTENT[type],
        overlayStyles: (styles) => {
            styles.opacity = 0.01;
        },
    });

    modal.show();

    return modal;
}

export function initialize(map) {
    let modal = showModal('help');

    window.addEventListener('dragover', handleDragOver, false);

    window.addEventListener('drop', e => {
        if (!modal.destroyed) {
            modal.destroy();
            modal.destroyed = true;
        }
        handleFileSelect(map, e);
    }, false);
}
<|endoftext|>


<commit_before>/* global Nauper, putDefaults, getTextOffset, wrapText, getTextHeight */
Nauper.UI = function UI(engine) {
  this.engine = engine;
  this.canvas = this.engine.canvas;
  this.render = this.engine.render;
  this.size = this.engine.size;
  this.menu = [];
  this.menuStyle = {};
  this.currentMS = 0;
  this.lastActive = undefined;
  this.menuOpened = false;
  this.menuIconStyle = {};
  this.animationTimeouts = [];

  this.drawTextLine = function drawTextLine(text, x, y, i) {
    let width = 0;
    if (i < text.length) {
      width = this.render.measureText(text.substring(0, i)).width;
      this.render.fillText(text[i], x + width, y);
      let tm = setTimeout.apply(this, [drawTextLine, this.engine.textDelay, text, x, y, i + 1]);
      this.animationTimeouts.push(tm);
    }
  }.bind(this);
};

Nauper.UI.prototype.setBackground = function setBackground(background) {
  if (background) {
    this.canvas.style.backgroundImage = `url(./data/images/backgrounds/${background})`;
  }
};

Nauper.UI.prototype.clearTimeouts = function clearTimeouts() {
  this.animationTimeouts.forEach((i) => {
    try {
      clearTimeouts(i);
    }
  });
};

Nauper.UI.prototype.drawTextBox = function drawTextBox(configs) {
  const defaults = {
    type: 'default',
    color: '#fff',
    link: '',
    x: 0.025,
    y: 0.80,
    height: 0.18,
    width: 0.95,
    radius: 0.05,
    callback: function __callback() {}
  };
  const conf = putDefaults(defaults, configs);
  const x = this.size.width * conf.x;
  const y = this.size.height * conf.y;
  const height = this.size.height * conf.height;
  const width = this.size.width * conf.width;
  const radius = this.size.height * conf.radius;

  this.render.fillStyle = conf.color;

  if (conf.type === 'default') {
    this.render.fillRect(x, y, width, height);
  } else if (conf.type === 'rounded') {
    this.render.beginPath();
    this.render.moveTo(x, y + radius);
    this.render.lineTo(x, (y + height) - radius);
    this.render.quadraticCurveTo(x, y + height, x + radius, y + height);
    this.render.lineTo((x + width) - radius, y + height);
    this.render.quadraticCurveTo(x + width, y + height, x + width, (y + height) - radius);
    this.render.lineTo(x + width, y + radius);
    this.render.quadraticCurveTo(x + width, y, (x + width) - radius, y);
    this.render.lineTo(x + radius, y);
    this.render.quadraticCurveTo(x, y, x, y + radius);
    this.render.fill();
  } else if (conf.type === 'image') {
    let image = new Image();
    image.addEventListener('load', (event) => {
      this.render.drawImage(image, x, y, width, height);
      if (conf.callback) {
        conf.callback(event);
      }
    });
    image.src = conf.link;
  }

  if (conf.type !== 'image') {
    conf.callback();
  }
};

Nauper.UI.prototype.drawText = function drawText(configs) {
  const defaults = {
    text: '',
    align: 'wrapped',
    color: '#000',
    animation: false,
    x: 0.10,
    y: 0.85,
    width: 0.80
  };
  const conf = putDefaults(defaults, configs);
  const x = this.size.width * conf.x;
  const y = this.size.height * conf.y;
  const maxwidth = this.size.width * conf.width;

  this.render.fillStyle = conf.color;
  if (conf.align === 'wrapped') {
    let texts = wrapText(this.render, conf.text, this.render.font, maxwidth);
    texts.result.forEach((i, j) => {
      if (conf.animation) {
        this.drawTextLine(i, x, y + (texts.height * j), 0);
      } else {
        this.render.fillText(i, x, y + (texts.height * j));
      }
    });
  } else if (conf.align === 'center') {
    let offset = getTextOffset(this.render, { width: this.canvas.width }, conf.text);
    this.render.fillText(conf.text, offset, y);
  }
};

Nauper.UI.prototype.checkMenuIconClick = function checkMenuIconClick(event) {
  let x = this.menuIconStyle.x * this.engine.size.width;
  let y = this.menuIconStyle.y * this.engine.size.width;
  let width = this.menuIconStyle.width * this.engine.size.width;
  let height = this.menuIconStyle.height * this.engine.size.width;
  if (event.pageX >= x && event.pageY >= y) {
    if (event.pageX <= x + width && event.pageY <= y + height) {
      return true;
    }
  }
  return false;
};

Nauper.UI.prototype.process = function process(event) {
  let result;
  let element = this.engine.elements[0][0];
  if (element === this.engine.element && !this.engine.firstPassed) {
    result = 'draw';
    this.engine.firstPassed = true;
  } else if (this.menuOpened) {
    for (let index = 0; index < this.menu[this.currentMS].length; index += 1) {
      let i = this.menu[this.currentMS][index];
      if (event.pageX >= i.x && event.pageX <= (i.x + i.width)) {
        if (event.pageY >= i.y && event.pageY <= (i.y + i.height)) {
          i.callback();
          break;
        }
      }
    }
    this.menuOpened = false;
    result = 'draw';
  } else if (this.checkMenuIconClick(event)) {
    this.drawMenu();
    result = null;
  } else {
    result = 'next';
  }
  return result;
};

Nauper.UI.prototype.move = function move(event) {
  if (this.engine.element.type === 'choice' && this.engine.ui.menuOpened === false) {
    let x = event.pageX;
    let y = event.pageY;
    let flag = false;
    for (let index = 0; index < this.engine.element.map.length; index += 1) {
      let sizes = {
        x: 0.025 * this.engine.size.width,
        y: ((index * 0.25) + 0.025) * this.engine.size.height,
        height: 0.20 * this.engine.size.height,
        width: 0.95 * this.engine.size.width
      };
      if (x >= sizes.x && x <= (sizes.x + sizes.width)) {
        if (y >= sizes.y && y <= (sizes.y + sizes.height)) {
          this.engine.element.active = index;
          flag = true;
          break;
        }
      }
    }
    if (!flag) {
      this.engine.element.active = undefined;
    }
    if (this.engine.element.active !== this.lastActive) {
      this.lastActive = this.engine.element.active;
      this.engine.element.draw(false);
      this.engine.ui.drawMenuIcon();
    }
  }
};

Nauper.UI.prototype.addMenuScreen = function addMenuScreen(menumap) {
  this.menu.push(menumap);
};

Nauper.UI.prototype.setMenuStyle = function setMenuStyle(sm) {
  const defaults = {
    mainbox: 'default',
    maincolor: '#fff',
    smallbox: 'default',
    smallcolor: '#eee',
    smallheight: 0.10,
    smallspace: 0.025,
    title: '#000',
    items: '#111'
  };
  this.menuStyle = putDefaults(defaults, sm);
};

Nauper.UI.prototype.drawMenu = function drawMenu() {
  let curSlength = this.menu[this.currentMS].length;
  let sh = this.menuStyle.smallheight;
  let ss = this.menuStyle.smallspace;
  let menu = {
    x: 0.125,
    height: ((sh + ss) * curSlength) + ss,
    width: 0.75
  };
  menu.y = (1 - menu.height) / 2;
  this.render.fillStyle = 'rgba(0, 0, 0, 0.7)';
  this.render.fillRect(0, 0, this.engine.size.width, this.engine.size.height);
  this.drawTextBox({
    type: this.menuStyle.mainbox,
    color: this.menuStyle.maincolor,
    x: menu.x,
    y: menu.y,
    width: menu.width,
    height: menu.height,
    callback: () => {
      this.menu[this.currentMS].forEach((i, index) => {
        let y = menu.y + (index * (sh + ss)) + ss;
        let width = menu.width - (ss * 2);
        this.menu[this.currentMS][index].x = (menu.x + ss) * this.size.width;
        this.menu[this.currentMS][index].y = y * this.size.height;
        this.menu[this.currentMS][index].width = width * this.size.width;
        this.menu[this.currentMS][index].height = this.menuStyle.smallheight * this.size.height;
        this.drawTextBox({
          type: this.menuStyle.smallbox,
          color: this.menuStyle.smallcolor,
          x: menu.x + ss,
          y,
          height: this.menuStyle.smallheight,
          width,
          callback: () => {
            let textHeight = getTextHeight(this.engine.font) / this.size.height;
            let textY = ((y + (this.menuStyle.smallheight / 2)) - (textHeight / 2)) + ss;
            this.drawText({
              align: 'center',
              color: this.menuStyle.items,
              y: textY,
              text: i.text
            });
          }
        });
      });
    }
  });
  this.menuOpened = true;
};

Nauper.UI.prototype.setMenuIconStyle = function setMenuIconStyle(mis) {
  const defaults = {
    type: 'default',
    x: 0,
    y: 0,
    width: 0.05,
    height: 0.05,
    radius: 0.01,
    link: '',
    color: '#efefef'
  };
  let style = putDefaults(defaults, mis);
  style.height *= this.engine.size.coff;
  style.y *= this.engine.size.coff;
  this.menuIconStyle = style;
};

Nauper.UI.prototype.drawMenuIcon = function drawMenuIcon() {
  this.drawTextBox(this.menuIconStyle);
};
<commit_msg>UI changes<commit_after>/* global Nauper, putDefaults, getTextOffset, wrapText, getTextHeight */
Nauper.UI = function UI(engine) {
  this.engine = engine;
  this.canvas = this.engine.canvas;
  this.render = this.engine.render;
  this.size = this.engine.size;
  this.menu = [];
  this.menuStyle = {};
  this.currentMS = 0;
  this.lastActive = undefined;
  this.menuOpened = false;
  this.menuIconStyle = {};
  this.animationTimeouts = [];

  this.drawTextLine = function drawTextLine(text, x, y, i) {
    let width = 0;
    if (i < text.length) {
      width = this.render.measureText(text.substring(0, i)).width;
      this.render.fillText(text[i], x + width, y);
      let tm = setTimeout.apply(this, [drawTextLine, this.engine.textDelay, text, x, y, i + 1]);
      this.animationTimeouts.push(tm);
    }
  }.bind(this);
};

Nauper.UI.prototype.setBackground = function setBackground(background) {
  if (background) {
    this.canvas.style.backgroundImage = `url(./data/images/backgrounds/${background})`;
  }
};

Nauper.UI.prototype.clearTimeouts = function clearTimeouts() {
  this.animationTimeouts.forEach((i) => {
    try {
      clearTimeouts(i);
    }
  });
  this.animationTimeouts = [];
};

Nauper.UI.prototype.drawTextBox = function drawTextBox(configs) {
  const defaults = {
    type: 'default',
    color: '#fff',
    link: '',
    x: 0.025,
    y: 0.80,
    height: 0.18,
    width: 0.95,
    radius: 0.05,
    callback: function __callback() {}
  };
  const conf = putDefaults(defaults, configs);
  const x = this.size.width * conf.x;
  const y = this.size.height * conf.y;
  const height = this.size.height * conf.height;
  const width = this.size.width * conf.width;
  const radius = this.size.height * conf.radius;

  this.render.fillStyle = conf.color;

  if (conf.type === 'default') {
    this.render.fillRect(x, y, width, height);
  } else if (conf.type === 'rounded') {
    this.render.beginPath();
    this.render.moveTo(x, y + radius);
    this.render.lineTo(x, (y + height) - radius);
    this.render.quadraticCurveTo(x, y + height, x + radius, y + height);
    this.render.lineTo((x + width) - radius, y + height);
    this.render.quadraticCurveTo(x + width, y + height, x + width, (y + height) - radius);
    this.render.lineTo(x + width, y + radius);
    this.render.quadraticCurveTo(x + width, y, (x + width) - radius, y);
    this.render.lineTo(x + radius, y);
    this.render.quadraticCurveTo(x, y, x, y + radius);
    this.render.fill();
  } else if (conf.type === 'image') {
    let image = new Image();
    image.addEventListener('load', (event) => {
      this.render.drawImage(image, x, y, width, height);
      if (conf.callback) {
        conf.callback(event);
      }
    });
    image.src = conf.link;
  }

  if (conf.type !== 'image') {
    conf.callback();
  }
};

Nauper.UI.prototype.drawText = function drawText(configs) {
  const defaults = {
    text: '',
    align: 'wrapped',
    color: '#000',
    animation: false,
    x: 0.10,
    y: 0.85,
    width: 0.80
  };
  const conf = putDefaults(defaults, configs);
  const x = this.size.width * conf.x;
  const y = this.size.height * conf.y;
  const maxwidth = this.size.width * conf.width;

  this.render.fillStyle = conf.color;
  if (conf.align === 'wrapped') {
    let texts = wrapText(this.render, conf.text, this.render.font, maxwidth);
    texts.result.forEach((i, j) => {
      if (conf.animation) {
        this.drawTextLine(i, x, y + (texts.height * j), 0);
      } else {
        this.render.fillText(i, x, y + (texts.height * j));
      }
    });
  } else if (conf.align === 'center') {
    let offset = getTextOffset(this.render, { width: this.canvas.width }, conf.text);
    this.render.fillText(conf.text, offset, y);
  }
};

Nauper.UI.prototype.checkMenuIconClick = function checkMenuIconClick(event) {
  let x = this.menuIconStyle.x * this.engine.size.width;
  let y = this.menuIconStyle.y * this.engine.size.width;
  let width = this.menuIconStyle.width * this.engine.size.width;
  let height = this.menuIconStyle.height * this.engine.size.width;
  if (event.pageX >= x && event.pageY >= y) {
    if (event.pageX <= x + width && event.pageY <= y + height) {
      return true;
    }
  }
  return false;
};

Nauper.UI.prototype.process = function process(event) {
  let result;
  let element = this.engine.elements[0][0];
  if (element === this.engine.element && !this.engine.firstPassed) {
    result = 'draw';
    this.engine.firstPassed = true;
  } else if (this.menuOpened) {
    for (let index = 0; index < this.menu[this.currentMS].length; index += 1) {
      let i = this.menu[this.currentMS][index];
      if (event.pageX >= i.x && event.pageX <= (i.x + i.width)) {
        if (event.pageY >= i.y && event.pageY <= (i.y + i.height)) {
          i.callback();
          break;
        }
      }
    }
    this.menuOpened = false;
    result = 'draw';
  } else if (this.checkMenuIconClick(event)) {
    this.drawMenu();
    result = null;
  } else {
    result = 'next';
  }
  return result;
};

Nauper.UI.prototype.move = function move(event) {
  if (this.engine.element.type === 'choice' && this.engine.ui.menuOpened === false) {
    let x = event.pageX;
    let y = event.pageY;
    let flag = false;
    for (let index = 0; index < this.engine.element.map.length; index += 1) {
      let sizes = {
        x: 0.025 * this.engine.size.width,
        y: ((index * 0.25) + 0.025) * this.engine.size.height,
        height: 0.20 * this.engine.size.height,
        width: 0.95 * this.engine.size.width
      };
      if (x >= sizes.x && x <= (sizes.x + sizes.width)) {
        if (y >= sizes.y && y <= (sizes.y + sizes.height)) {
          this.engine.element.active = index;
          flag = true;
          break;
        }
      }
    }
    if (!flag) {
      this.engine.element.active = undefined;
    }
    if (this.engine.element.active !== this.lastActive) {
      this.lastActive = this.engine.element.active;
      this.engine.element.draw(false);
      this.engine.ui.drawMenuIcon();
    }
  }
};

Nauper.UI.prototype.addMenuScreen = function addMenuScreen(menumap) {
  this.menu.push(menumap);
};

Nauper.UI.prototype.setMenuStyle = function setMenuStyle(sm) {
  const defaults = {
    mainbox: 'default',
    maincolor: '#fff',
    smallbox: 'default',
    smallcolor: '#eee',
    smallheight: 0.10,
    smallspace: 0.025,
    title: '#000',
    items: '#111'
  };
  this.menuStyle = putDefaults(defaults, sm);
};

Nauper.UI.prototype.drawMenu = function drawMenu() {
  let curSlength = this.menu[this.currentMS].length;
  let sh = this.menuStyle.smallheight;
  let ss = this.menuStyle.smallspace;
  let menu = {
    x: 0.125,
    height: ((sh + ss) * curSlength) + ss,
    width: 0.75
  };
  menu.y = (1 - menu.height) / 2;
  this.render.fillStyle = 'rgba(0, 0, 0, 0.7)';
  this.render.fillRect(0, 0, this.engine.size.width, this.engine.size.height);
  this.drawTextBox({
    type: this.menuStyle.mainbox,
    color: this.menuStyle.maincolor,
    x: menu.x,
    y: menu.y,
    width: menu.width,
    height: menu.height,
    callback: () => {
      this.menu[this.currentMS].forEach((i, index) => {
        let y = menu.y + (index * (sh + ss)) + ss;
        let width = menu.width - (ss * 2);
        this.menu[this.currentMS][index].x = (menu.x + ss) * this.size.width;
        this.menu[this.currentMS][index].y = y * this.size.height;
        this.menu[this.currentMS][index].width = width * this.size.width;
        this.menu[this.currentMS][index].height = this.menuStyle.smallheight * this.size.height;
        this.drawTextBox({
          type: this.menuStyle.smallbox,
          color: this.menuStyle.smallcolor,
          x: menu.x + ss,
          y,
          height: this.menuStyle.smallheight,
          width,
          callback: () => {
            let textHeight = getTextHeight(this.engine.font) / this.size.height;
            let textY = ((y + (this.menuStyle.smallheight / 2)) - (textHeight / 2)) + ss;
            this.drawText({
              align: 'center',
              color: this.menuStyle.items,
              y: textY,
              text: i.text
            });
          }
        });
      });
    }
  });
  this.menuOpened = true;
};

Nauper.UI.prototype.setMenuIconStyle = function setMenuIconStyle(mis) {
  const defaults = {
    type: 'default',
    x: 0,
    y: 0,
    width: 0.05,
    height: 0.05,
    radius: 0.01,
    link: '',
    color: '#efefef'
  };
  let style = putDefaults(defaults, mis);
  style.height *= this.engine.size.coff;
  style.y *= this.engine.size.coff;
  this.menuIconStyle = style;
};

Nauper.UI.prototype.drawMenuIcon = function drawMenuIcon() {
  this.drawTextBox(this.menuIconStyle);
};
<|endoftext|>


<commit_before>(function() {
  /**
   * The CPU of the emulator, responsible for processing memory opcodes.
   *
   * @class Chip8.VM
   * @constructor
   */
  var VM = function VM() {
    this.pc = 0x200;
    this.stack = new Array;
    this.screen = { clear: function() {}, render: function() {}, setPixel: function() {} };
    this.input = { isKeyPressed: function(key) {} };
    this.v = new Uint8Array(16);
    this.i = 0;
    this.memory = new Uint8Array(4096);
    this.delayTimer = 0;
    this.soundTimer = 0;
    this.paused = false;
    this.speed = 10;

    /**
     * Loads the CHIP8 fonts into memory.
     * Detailed information can be found at: http://devernay.free.fr/hacks/chip8/C8TECH10.HTM#font
     * @method loadFonts
     * @private
     */
    var loadFonts = function() {
      var fonts = [
        0xF0, 0x90, 0x90, 0x90, 0xF0, // 0
        0x20, 0x60, 0x20, 0x20, 0x70, // 1
        0xF0, 0x10, 0xF0, 0x80, 0xF0, // 2
        0xF0, 0x10, 0xF0, 0x10, 0xF0, // 3
        0x90, 0x90, 0xF0, 0x10, 0x10, // 4
        0xF0, 0x80, 0xF0, 0x10, 0xF0, // 5
        0xF0, 0x80, 0xF0, 0x90, 0xF0, // 6
        0xF0, 0x10, 0x20, 0x40, 0x40, // 7
        0xF0, 0x90, 0xF0, 0x90, 0xF0, // 8
        0xF0, 0x90, 0xF0, 0x10, 0xF0, // 9
        0xF0, 0x90, 0xF0, 0x90, 0x90, // A
        0xE0, 0x90, 0xE0, 0x90, 0xE0, // B
        0xF0, 0x80, 0x80, 0x80, 0xF0, // C
        0xE0, 0x90, 0x90, 0x90, 0xE0, // D
        0xF0, 0x80, 0xF0, 0x80, 0xF0, // E
        0xF0, 0x80, 0xF0, 0x80, 0x80  // F
      ];

      for (var i = 0, length = fonts.length; i < length; i++) {
        this.memory[i] = fonts[i];
      }
    }

    /**
     * Reset the VM with the default values.
     * @method reset
     */
    this.reset = function() {
      this.pc = 0x200;
      this.stack = new Array;
      this.v = new Uint8Array(16);
      this.i = 0;
      this.memory = new Uint8Array(4096);
      this.delayTimer = 0;
      this.soundTimer = 0;
      this.screen.clear();
      this.input.clear();
      loadFonts();
      this.paused = false;
    }

    /**
     * Loads a program into the memory.
     * @method loadProgram
     * @param {Array} program array of bytes of a CHIP8 program
     */
    this.loadProgram = function(program) {
      for (var i = 0, length = program.length; i < length; i++) {
        this.memory[0x200 + i] = program[i];
      }
    }

    /**
     * The main CPU cycle.
     * @method cycle
     */
    this.cycle = function() {
      for (var i = 0; i < this.speed; i++) {
        if (!this.paused) {
          var opcode = this.memory[this.pc] << 8 | this.memory[this.pc + 1];
          this.perform(opcode);
          this.updateTimers();
        }
      }

      this.render();
    }

    /**
     * Process a given opcode.
     * @method perform
     * @param {Integer} opcode
     */
    this.perform = function(opcode) {
      this.pc += 2;

      var x = (opcode & 0x0F00) >> 8;
      var y = (opcode & 0x00F0) >> 4;

      switch (opcode & 0xF000) {
        case 0x000:
          switch(opcode) {
            case 0x00E0:
              this.screen.clear();
              break;

            case 0x00EE:
              this.pc = this.stack.pop();
              break;
          }
          break;

        case 0x1000:
          this.pc = opcode & 0x0FFF;
          break;

        case 0x2000:
          this.stack.push(this.pc);
          this.pc = opcode & 0x0FFF;
          break;

        case 0x3000:
          if (this.v[x] == (opcode & 0x00FF)) {
            this.pc += 2;
          }
          break;

        case 0x4000:
          if (this.v[x] != (opcode & 0x00FF)) {
            this.pc += 2;
          }
          break;

        case 0x5000:
          if (this.v[x] == this.v[y]) {
            this.pc += 2;
          }
          break;

        case 0x6000:
          this.v[x] = opcode & 0x00FF;
          break;

        case 0x7000:
          this.v[x] += opcode & 0x00FF;
          break;

        case 0x8000:
          switch (opcode & 0x000F) {
            case 0x0000:
              this.v[x] = this.v[y];
              break;

            case 0x0001:
              this.v[x] = this.v[x] | this.v[y];
              break;

            case 0x0002:
              this.v[x] = this.v[x] & this.v[y];
              break;

            case 0x0003:
              this.v[x] = this.v[x] ^ this.v[y];
              break;

            case 0x0004:
              var sum = this.v[x] + this.v[y];

              if (sum > 0xFF) {
                this.v[0xF] = 1;
              } else {
                this.v[0xF] = 0;
              }

              this.v[x] = sum;
              break;

            case 0x0005:
              if(this.v[x] > this.v[y]) {
                this.v[0xF] = 1;
              } else {
                this.v[0xF] = 0;
              }

              this.v[x] = this.v[x] - this.v[y];
              break;

            case 0x0006:
              this.v[0xF] = this.v[x] & 0x01;
              this.v[x] = this.v[x] >> 1;
              break;

            case 0x0007:
              if (this.v[x] > this.v[y]) {
                this.v[0xF] = 0;
              } else {
                this.v[0xF] = 1;
              }

              this.v[x] = this.v[y] - this.v[x];
              break;

            case 0x000E:
              this.v[0xF] = this.v[x] & 0x80;
              this.v[x] = this.v[x] << 1;
              break;
          }
          break;

        case 0x9000:
          if (this.v[x] != this.v[y]) this.pc += 2;
          break;

        case 0xA000:
          this.i = opcode & 0x0FFF;
          break;

        case 0xB000:
          this.pc = (opcode & 0x0FFF) + this.v[0];
          break;

        case 0xC000:
          this.v[x] = Math.floor(Math.random() * 0xFF) & (opcode & 0x00FF);
          break;

        case 0xD000:
          var row, col, sprite
            , width = 8
            , height = opcode & 0x000F;

          this.v[0xF] = 0;

          for (row = 0; row < height; row++) {
            sprite = this.memory[this.i + row];

            for (col = 0; col < width; col++) {
              if ((sprite & 0x80) > 0) {
                if (this.screen.setPixel(this.v[x] + col, this.v[y] + row)) {
                  this.v[0xF] = 1;
                }
              }

              sprite = sprite << 1;
            }
          }

          break;

        case 0xE000:
          switch (opcode & 0x00FF) {
            case 0x009E:
              if (this.input.isKeyPressed(this.v[x])) {
                this.pc += 2;
              }
              break;

            case 0x00A1:
              if (!this.input.isKeyPressed(this.v[x])) {
                this.pc += 2;
              }
              break;
          }
          break;

        case 0xF000:
          switch (opcode & 0x00FF) {
            case 0x0007:
              this.v[x] = this.delayTimer;
              break;

            case 0x000A:
              this.paused = true;

              this.input.onKeyPress = function(key) {
                this.v[x] = key;
                this.paused = false;
                this.input.onKeyPress = function() {}
              }.bind(this);

            case 0x0015:
              this.delayTimer = this.v[x];
              break;

            case 0x0018:
              this.soundTimer = this.v[x];
              break;

            case 0x0029:
              this.i = this.v[x] * 5;
              break;

            case 0x0033:
              this.memory[this.i]     = parseInt(this.v[x] / 100);
              this.memory[this.i + 1] = parseInt(this.v[x] % 100 / 10);
              this.memory[this.i + 2] = this.v[x] % 10;
              break;

            case 0x0055:
              for (var i = 0; i <= x; i++) {
                this.memory[this.i + i] = this.v[i];
              }
              break;

            case 0x0065:
              for (var i = 0; i <= x; i++) {
                this.v[i] = this.memory[this.i + i];
              }
              break;

            case 0x001E:
              this.i += this.v[x];
              break;
          }

          break;

        default:
          throw new Error("Unknow opcode " + opcode.toString(16) + " informed.");
      }
    }

    /**
     * Renders the screen.
     * @method render
     */
    this.render = function() {
      this.screen.render();
    }

    /**
     * Updates the CPU delay and sound timers.
     * More info at: http://devernay.free.fr/hacks/chip8/C8TECH10.HTM#2.5
     * @method updateTimers
     */
    this.updateTimers = function() {
      if (this.delayTimer > 0) this.delayTimer -= 1;
      if (this.soundTimer > 0) this.soundTimer -= 1;
    }
  }

  if (typeof module != "undefined") {
    module.exports = VM;
  } else {
    window.Chip8.VM = VM;
  }
})();
<commit_msg>Documenting the opcodes.<commit_after>(function() {
  /**
   * The CPU of the emulator, responsible for processing memory opcodes.
   *
   * @class Chip8.VM
   * @constructor
   */
  var VM = function VM() {
    this.pc = 0x200;
    this.stack = new Array;
    this.screen = { clear: function() {}, render: function() {}, setPixel: function() {} };
    this.input = { isKeyPressed: function(key) {} };
    this.v = new Uint8Array(16);
    this.i = 0;
    this.memory = new Uint8Array(4096);
    this.delayTimer = 0;
    this.soundTimer = 0;
    this.paused = false;
    this.speed = 10;

    /**
     * Loads the CHIP8 fonts into memory.
     * Detailed information can be found at: http://devernay.free.fr/hacks/chip8/C8TECH10.HTM#font
     * @method loadFonts
     * @private
     */
    var loadFonts = function() {
      var fonts = [
        0xF0, 0x90, 0x90, 0x90, 0xF0, // 0
        0x20, 0x60, 0x20, 0x20, 0x70, // 1
        0xF0, 0x10, 0xF0, 0x80, 0xF0, // 2
        0xF0, 0x10, 0xF0, 0x10, 0xF0, // 3
        0x90, 0x90, 0xF0, 0x10, 0x10, // 4
        0xF0, 0x80, 0xF0, 0x10, 0xF0, // 5
        0xF0, 0x80, 0xF0, 0x90, 0xF0, // 6
        0xF0, 0x10, 0x20, 0x40, 0x40, // 7
        0xF0, 0x90, 0xF0, 0x90, 0xF0, // 8
        0xF0, 0x90, 0xF0, 0x10, 0xF0, // 9
        0xF0, 0x90, 0xF0, 0x90, 0x90, // A
        0xE0, 0x90, 0xE0, 0x90, 0xE0, // B
        0xF0, 0x80, 0x80, 0x80, 0xF0, // C
        0xE0, 0x90, 0x90, 0x90, 0xE0, // D
        0xF0, 0x80, 0xF0, 0x80, 0xF0, // E
        0xF0, 0x80, 0xF0, 0x80, 0x80  // F
      ];

      for (var i = 0, length = fonts.length; i < length; i++) {
        this.memory[i] = fonts[i];
      }
    }

    /**
     * Reset the VM with the default values.
     * @method reset
     */
    this.reset = function() {
      this.pc = 0x200;
      this.stack = new Array;
      this.v = new Uint8Array(16);
      this.i = 0;
      this.memory = new Uint8Array(4096);
      this.delayTimer = 0;
      this.soundTimer = 0;
      this.screen.clear();
      this.input.clear();
      loadFonts();
      this.paused = false;
    }

    /**
     * Loads a program into the memory.
     * @method loadProgram
     * @param {Array} program array of bytes of a CHIP8 program
     */
    this.loadProgram = function(program) {
      for (var i = 0, length = program.length; i < length; i++) {
        this.memory[0x200 + i] = program[i];
      }
    }

    /**
     * The main CPU cycle.
     * @method cycle
     */
    this.cycle = function() {
      for (var i = 0; i < this.speed; i++) {
        if (!this.paused) {
          var opcode = this.memory[this.pc] << 8 | this.memory[this.pc + 1];
          this.perform(opcode);
          this.updateTimers();
        }
      }

      this.render();
    }

    /**
     * Process a given opcode.
     * @method perform
     * @param {Integer} opcode
     */
    this.perform = function(opcode) {
      this.pc += 2;

      var x = (opcode & 0x0F00) >> 8;
      var y = (opcode & 0x00F0) >> 4;

      switch (opcode & 0xF000) {
        case 0x000:
          switch(opcode) {
            // 00E0 - CLS
            // Clear the display.
            case 0x00E0:
              this.screen.clear();
              break;

            // 00EE - RET
            // Return from a subroutine.
            // The interpreter sets the program counter to the address at the
            //   top of the stack, then subtracts 1 from the stack pointer.
            case 0x00EE:
              this.pc = this.stack.pop();
              break;
          }
          break;

        // 1nnn - JP addr
        // Jump to location nnn.
        // The interpreter sets the program counter to nnn.
        case 0x1000:
          this.pc = opcode & 0x0FFF;
          break;

        // 2nnn - CALL addr
        // Call subroutine at nnn.
        // The interpreter increments the stack pointer, then puts the current
        //   PC on the top of the stack. The PC is then set to nnn.
        case 0x2000:
          this.stack.push(this.pc);
          this.pc = opcode & 0x0FFF;
          break;

        // 3xkk - SE Vx, byte
        // Skip next instruction if Vx = kk.
        // The interpreter compares register Vx to kk, and if they are equal,
        //   increments the program counter by 2.
        case 0x3000:
          if (this.v[x] == (opcode & 0x00FF)) {
            this.pc += 2;
          }
          break;

        // 4xkk - SNE Vx, byte
        // Skip next instruction if Vx != kk.
        // The interpreter compares register Vx to kk, and if they are not
        //   equal, increments the program counter by 2.
        case 0x4000:
          if (this.v[x] != (opcode & 0x00FF)) {
            this.pc += 2;
          }
          break;

        // 5xy0 - SE Vx, Vy
        // Skip next instruction if Vx = Vy.
        // The interpreter compares register Vx to register Vy, and if they are
        //   equal, increments the program counter by 2.
        case 0x5000:
          if (this.v[x] == this.v[y]) {
            this.pc += 2;
          }
          break;

        // 6xkk - LD Vx, byte
        // Set Vx = kk.
        // The interpreter puts the value kk into register Vx.
        case 0x6000:
          this.v[x] = opcode & 0x00FF;
          break;

        // 7xkk - ADD Vx, byte
        // Set Vx = Vx + kk.
        // Adds the value kk to the value of register Vx, then stores the
        //   result in Vx.
        case 0x7000:
          this.v[x] += opcode & 0x00FF;
          break;

        case 0x8000:
          switch (opcode & 0x000F) {
            // 8xy0 - LD Vx, Vy
            // Set Vx = Vy.
            // Stores the value of register Vy in register Vx.
            case 0x0000:
              this.v[x] = this.v[y];
              break;

            // 8xy1 - OR Vx, Vy
            // Set Vx = Vx OR Vy.
            // Performs a bitwise OR on the values of Vx and Vy, then stores the
            //   result in Vx. A bitwise OR compares the corrseponding
            //   bits from two values, and if either bit is 1, then the same bit
            //   in the result is also 1. Otherwise, it is 0.
            case 0x0001:
              this.v[x] = this.v[x] | this.v[y];
              break;

            // 8xy2 - AND Vx, Vy
            // Set Vx = Vx AND Vy.
            // Performs a bitwise AND on the values of Vx and Vy, then stores
            //   the result in Vx. A bitwise AND compares the corrseponding
            //   bits from two values, and if both bits are 1, then the same
            //   bit in the result is also 1. Otherwise, it is 0.
            case 0x0002:
              this.v[x] = this.v[x] & this.v[y];
              break;

            // 8xy3 - XOR Vx, Vy
            // Set Vx = Vx XOR Vy.
            // Performs a bitwise exclusive OR on the values of Vx and Vy, then
            //   stores the result in Vx. An exclusive OR compares the
            //   corrseponding bits from two values, and if the bits are not
            //   both the same, then the corresponding bit in the result is set
            //   to 1. Otherwise, it is 0.
            case 0x0003:
              this.v[x] = this.v[x] ^ this.v[y];
              break;

            // 8xy4 - ADD Vx, Vy
            // Set Vx = Vx + Vy, set VF = carry.
            // The values of Vx and Vy are added together. If the result is
            //   greater than 8 bits (i.e., > 255,) VF is set to 1,
            //   otherwise 0. Only the lowest 8 bits of the result are kept,
            //   and stored in Vx.
            case 0x0004:
              var sum = this.v[x] + this.v[y];

              if (sum > 0xFF) {
                this.v[0xF] = 1;
              } else {
                this.v[0xF] = 0;
              }

              this.v[x] = sum;
              break;

            // 8xy5 - SUB Vx, Vy
            // Set Vx = Vx - Vy, set VF = NOT borrow.
            // If Vx > Vy, then VF is set to 1, otherwise 0. Then Vy is
            //   subtracted from Vx, and the results stored in Vx.
            case 0x0005:
              if(this.v[x] > this.v[y]) {
                this.v[0xF] = 1;
              } else {
                this.v[0xF] = 0;
              }

              this.v[x] = this.v[x] - this.v[y];
              break;

            // 8xy6 - SHR Vx {, Vy}
            // Set Vx = Vx SHR 1.
            // If the least-significant bit of Vx is 1, then VF is set to 1,
            //   otherwise 0. Then Vx is divided by 2.
            case 0x0006:
              this.v[0xF] = this.v[x] & 0x01;
              this.v[x] = this.v[x] >> 1;
              break;

            // 8xy7 - SUBN Vx, Vy
            // Set Vx = Vy - Vx, set VF = NOT borrow.
            // If Vy > Vx, then VF is set to 1, otherwise 0. Then Vx is
            //   subtracted from Vy, and the results stored in Vx.
            case 0x0007:
              if (this.v[x] > this.v[y]) {
                this.v[0xF] = 0;
              } else {
                this.v[0xF] = 1;
              }

              this.v[x] = this.v[y] - this.v[x];
              break;

            // 8xyE - SHL Vx {, Vy}
            // Set Vx = Vx SHL 1.
            // If the most-significant bit of Vx is 1, then VF is set to 1,
            //   otherwise to 0. Then Vx is multiplied by 2.
            case 0x000E:
              this.v[0xF] = this.v[x] & 0x80;
              this.v[x] = this.v[x] << 1;
              break;
          }
          break;

        // 9xy0 - SNE Vx, Vy
        // Skip next instruction if Vx != Vy.
        // The values of Vx and Vy are compared, and if they are not equal, the
        //   program counter is increased by 2.
        case 0x9000:
          if (this.v[x] != this.v[y]) this.pc += 2;
          break;

        // Annn - LD I, addr
        // Set I = nnn.
        // The value of register I is set to nnn.
        case 0xA000:
          this.i = opcode & 0x0FFF;
          break;

        // Bnnn - JP V0, addr
        // Jump to location nnn + V0.
        // The program counter is set to nnn plus the value of V0.
        case 0xB000:
          this.pc = (opcode & 0x0FFF) + this.v[0];
          break;

        // Cxkk - RND Vx, byte
        // Set Vx = random byte AND kk.
        // The interpreter generates a random number from 0 to 255, which is
        //   then ANDed with the value kk. The results are stored in Vx. See
        //   instruction 8xy2 for more information on AND.
        case 0xC000:
          this.v[x] = Math.floor(Math.random() * 0xFF) & (opcode & 0x00FF);
          break;

        // Dxyn - DRW Vx, Vy, nibble
        // Display n-byte sprite starting at memory location I at (Vx, Vy),
        //   set VF = collision.
        case 0xD000:
          var row, col, sprite
            , width = 8
            , height = opcode & 0x000F;

          this.v[0xF] = 0;

          for (row = 0; row < height; row++) {
            sprite = this.memory[this.i + row];

            for (col = 0; col < width; col++) {
              if ((sprite & 0x80) > 0) {
                if (this.screen.setPixel(this.v[x] + col, this.v[y] + row)) {
                  this.v[0xF] = 1;
                }
              }

              sprite = sprite << 1;
            }
          }

          break;

        case 0xE000:
          switch (opcode & 0x00FF) {
            // Ex9E - SKP Vx
            // Skip next instruction if key with the value of Vx is pressed.
            case 0x009E:
              if (this.input.isKeyPressed(this.v[x])) {
                this.pc += 2;
              }
              break;

            // ExA1 - SKNP Vx
            // Skip next instruction if key with the value of Vx is not pressed.
            case 0x00A1:
              if (!this.input.isKeyPressed(this.v[x])) {
                this.pc += 2;
              }
              break;
          }
          break;

        case 0xF000:
          switch (opcode & 0x00FF) {
            // Fx07 - LD Vx, DT
            // Set Vx = delay timer value.
            case 0x0007:
              this.v[x] = this.delayTimer;
              break;

            // Fx0A - LD Vx, K
            // Wait for a key press, store the value of the key in Vx.
            case 0x000A:
              this.paused = true;

              this.input.onKeyPress = function(key) {
                this.v[x] = key;
                this.paused = false;
                this.input.onKeyPress = function() {}
              }.bind(this);

            // Fx15 - LD DT, Vx
            // Set delay timer = Vx.
            case 0x0015:
              this.delayTimer = this.v[x];
              break;

            // Fx18 - LD ST, Vx
            // Set sound timer = Vx.
            case 0x0018:
              this.soundTimer = this.v[x];
              break;

            // Fx29 - LD F, Vx
            // Set I = location of sprite for digit Vx.
            case 0x0029:
              this.i = this.v[x] * 5;
              break;

            // Fx33 - LD B, Vx
            // Store BCD representation of Vx in memory locations I,
            //   I+1, and I+2.
            case 0x0033:
              this.memory[this.i]     = parseInt(this.v[x] / 100);
              this.memory[this.i + 1] = parseInt(this.v[x] % 100 / 10);
              this.memory[this.i + 2] = this.v[x] % 10;
              break;

            // Fx55 - LD [I], Vx
            // Store registers V0 through Vx in memory starting at location I.
            case 0x0055:
              for (var i = 0; i <= x; i++) {
                this.memory[this.i + i] = this.v[i];
              }
              break;

            // Fx65 - LD Vx, [I]
            // Read registers V0 through Vx from memory starting at location I.
            case 0x0065:
              for (var i = 0; i <= x; i++) {
                this.v[i] = this.memory[this.i + i];
              }
              break;

            // Fx1E - ADD I, Vx
            // Set I = I + Vx.
            case 0x001E:
              this.i += this.v[x];
              break;
          }

          break;

        default:
          throw new Error("Unknow opcode " + opcode.toString(16) + " informed.");
      }
    }

    /**
     * Renders the screen.
     * @method render
     */
    this.render = function() {
      this.screen.render();
    }

    /**
     * Updates the CPU delay and sound timers.
     * More info at: http://devernay.free.fr/hacks/chip8/C8TECH10.HTM#2.5
     * @method updateTimers
     */
    this.updateTimers = function() {
      if (this.delayTimer > 0) this.delayTimer -= 1;
      if (this.soundTimer > 0) this.soundTimer -= 1;
    }
  }

  if (typeof module != "undefined") {
    module.exports = VM;
  } else {
    window.Chip8.VM = VM;
  }
})();
<|endoftext|>


<commit_before>import storage from 'src/database/storage';
import File from './file';

/* Constants */
const ORGANIZATION_ID = 0;
const ROOT_FOLDER_ID = 0;
const NO_PARENT_ID = -1;

/**
* Folder Object
*
* @param {Number} folderID
* @param {String} folderName
* @param {String} folderPath
*/

function Folder(folderID, folderName, folderPath) {
  this.id = folderID;
  this.name = folderName;
  this.path = folderPath;
  this.parentFolder = null;
  this.childFolderList = []; // list of folders in current folder
  this.childFileList = []; // list of files in current folder
  console.log('new Folder created');
}

/**
 * Create a new folder in this folder instance
 *
 * @param {String} folderName
 * @return {Folder}
 */
Folder.prototype.createFolder = function createFolder(folderName) {
  console.log('folder.createFolder');
  const newFolderPath = `${this.folderPath}/${folderName}`;
  return storage.createFolder(ORGANIZATION_ID, newFolderPath, this.id)
  .then((dbFolderObj) => {
    const newFolder = new Folder(dbFolderObj.folder_ID, dbFolderObj.folderName, dbFolderObj.folderPath);
    newFolder.parentFolder = this;
    this.childFolderList.push(newFolder);
    return newFolder;
  })
  .catch((error) => {
    throw error;
  });
};

/**
 * Create a new file in this folder in this folder instance
 *
 * @param {String} fileName
 * @return {File}
 */
Folder.prototype.createFile = function createFile(fileName) {
  console.log('folder.createFile');
  const newFilePath = `${this.folderPath}/${fileName}`;
  return storage.createFile(ORGANIZATION_ID, newFilePath, this.id)
  .then((dbFileObj) => {
    const newFile = new File(dbFileObj.file_id, dbFileObj.file_name, dbFileObj.file_path, this);
    this.childFileList.push(newFile);
  })
  .catch((error) => {
    throw error;
  });
};

/**
 * Remove a folder from the database
 *
 * @param {}
 * @return {}
 */
Folder.prototype.remove = function remove() {
  console.log('folder.remove');
  if (this.id === ROOT_FOLDER_ID) {
    throw new Error('Cannot remove root folder.');
  }
  return storage.deleteFolder(this.id)
  .then(() => {
    const parentFolder = this.parentFolder;
    const index = parentFolder.childFolderList.findIndex(childFolder => childFolder.id === this.id);
    parentFolder.childFolderList.splice(index, 1);
  });
};

/**
 * Rename the folder in the database
 *
 * @param {String} newFolderName
 * @return {}
 */
Folder.prototype.rename = function rename(newFolderName) {
  console.log('folder.next');
  this.folderName = newFolderName;
  // update folder name in database
};

/* Private Functions */
function queueIsEmpty(queue) {
  return queue.length <= 0;
}

function removeElementAtIndex(queue, index) {
  if (index === NO_PARENT_ID) {
    return null;
  } else {
    return queue.splice(index, 1);
  }
}

function getChildFile(queue, parentID) {
  parentID = parentID || NO_PARENT_ID;
  const index = queue.findIndex(dbFileObj => dbFileObj.folder_ID === parentID);
  return removeElementAtIndex(queue, index);
}

function getChildFolder(queue, parentID) {
  parentID = parentID || NO_PARENT_ID;
  const index = queue.findIndex(dbFolderObj => dbFolderObj.parent_folder_ID === parentID);
  return removeElementAtIndex(queue, index);
}

/* Folder static function */
const folderOperation = {
  /**
   * Returns the root folder
   *
   * @return {Folder}
   */
  getRootFolder: function getRootFolder() {
    console.log('folderOperation.getRootFolder');
    // to be changed when online platform is implemented

    return storage.initializeDatabase()
    .then(() => storage.loadAllFilesAndFolders()
      .then((dbList) => {
        const dbFileList = dbList.shift(); // list of File from database list
        const dbFolderList = dbList.shift(); // list of Folder from datbase list

        const processingQueue = [];
        const rootFolder = getChildFolder(dbFolderList);
        processingQueue.push(new Folder(rootFolder.folder_ID, rootFolder.folder_name, rootFolder.folder_path));

        let currFolder;
        let dbFileObj;
        let dbFolderObj;
        let childFile;
        let childFolder;
        while (!queueIsEmpty(processingQueue)) {
          currFolder = processingQueue.shift();

      /* Process dbFileList for child file */
          while ((dbFileObj = getChildFile(dbFileList, currFolder.id)) !== null) {
            childFile = new File(dbFileObj.file_ID, dbFileObj.file_name, dbFileObj.file_path);
            currFolder.childFileList.push(childFile);
          }

      /* Process dbFolderList for child folder */
          while ((dbFolderObj = getChildFolder(dbFolderList, currFolder.id)) !== null) {
            processingQueue.push(dbFolderObj);
            childFolder = new Folder(dbFolderObj.folder_ID, dbFolderObj.folder_name, dbFolderObj.folder_path);
            childFolder.parentFolder = currFolder;
            currFolder.childFolderList.push(childFolder);
          }
        }
        return rootFolder;
      })
      .catch((error) => {
        throw error;
      }))
    .catch((error) => {
      throw error;
    });
  }

};

export default folderOperation;
<commit_msg>Corrected wrong variable names.<commit_after>import storage from 'src/database/storage';
import File from './file';

/* Constants */
const ORGANIZATION_ID = 0;
const ROOT_FOLDER_ID = 0;
const NO_PARENT_ID = -1;

/**
* Folder Object
*
* @param {Number} folderID
* @param {String} folderName
* @param {String} folderPath
*/

function Folder(folderID, folderName, folderPath) {
  this.id = folderID;
  this.name = folderName;
  this.path = folderPath;
  this.parentFolder = null;
  this.childFolderList = []; // list of folders in current folder
  this.childFileList = []; // list of files in current folder
  console.log('new Folder created');
}

/**
 * Create a new folder in this folder instance
 *
 * @param {String} folderName
 * @return {Folder}
 */
Folder.prototype.createFolder = function createFolder(folderName) {
  console.log('folder.createFolder');
  const newFolderPath = `${this.path}/${folderName}`;
  return storage.createFolder(ORGANIZATION_ID, newFolderPath, this.id)
  .then((dbFolderObj) => {
    const newFolder = new Folder(dbFolderObj.folder_ID, dbFolderObj.folder_name, dbFolderObj.folder_path);
    newFolder.parentFolder = this;
    this.childFolderList.push(newFolder);
    return newFolder;
  })
  .catch((error) => {
    throw error;
  });
};

/**
 * Create a new file in this folder in this folder instance
 *
 * @param {String} fileName
 * @return {File}
 */
Folder.prototype.createFile = function createFile(fileName) {
  console.log('folder.createFile');
  const newFilePath = `${this.path}/${fileName}`;
  return storage.createFile(ORGANIZATION_ID, newFilePath, this.id)
  .then((dbFileObj) => {
    const newFile = new File(dbFileObj.file_id, dbFileObj.file_name, dbFileObj.file_path, this);
    this.childFileList.push(newFile);
  })
  .catch((error) => {
    throw error;
  });
};

/**
 * Remove a folder from the database
 *
 * @param {}
 * @return {}
 */
Folder.prototype.remove = function remove() {
  console.log('folder.remove');
  if (this.id === ROOT_FOLDER_ID) {
    throw new Error('Cannot remove root folder.');
  }
  return storage.deleteFolder(this.id)
  .then(() => {
    const parentFolder = this.parentFolder;
    const index = parentFolder.childFolderList.findIndex(childFolder => childFolder.id === this.id);
    parentFolder.childFolderList.splice(index, 1);
  });
};

/**
 * Rename the folder in the database
 *
 * @param {String} newFolderName
 * @return {}
 */
Folder.prototype.rename = function rename(newFolderName) {
  console.log('folder.next');
  this.folderName = newFolderName;
  // update folder name in database
};

/* Private Functions */
function queueIsEmpty(queue) {
  return queue.length <= 0;
}

function removeElementAtIndex(queue, index) {
  if (index === NO_PARENT_ID) {
    return null;
  } else {
    return queue.splice(index, 1);
  }
}

function getChildFile(queue, parentID) {
  parentID = parentID || NO_PARENT_ID;
  const index = queue.findIndex(dbFileObj => dbFileObj.folder_ID === parentID);
  return removeElementAtIndex(queue, index);
}

function getChildFolder(queue, parentID) {
  parentID = parentID || NO_PARENT_ID;
  const index = queue.findIndex(dbFolderObj => dbFolderObj.parent_folder_ID === parentID);
  return removeElementAtIndex(queue, index);
}

/* Folder static function */
const folderOperation = {
  /**
   * Returns the root folder
   *
   * @return {Folder}
   */
  getRootFolder: function getRootFolder() {
    console.log('folderOperation.getRootFolder');
    // to be changed when online platform is implemented

    return storage.initializeDatabase()
    .then(() => storage.loadAllFilesAndFolders()
      .then((dbList) => {
        const dbFileList = dbList.shift(); // list of File from database list
        const dbFolderList = dbList.shift(); // list of Folder from datbase list

        const processingQueue = [];
        const rootFolder = getChildFolder(dbFolderList);
        processingQueue.push(new Folder(rootFolder.folder_ID, rootFolder.folder_name, rootFolder.folder_path || ''));

        let currFolder;
        let dbFileObj;
        let dbFolderObj;
        let childFile;
        let childFolder;
        while (!queueIsEmpty(processingQueue)) {
          currFolder = processingQueue.shift();

      /* Process dbFileList for child file */
          while ((dbFileObj = getChildFile(dbFileList, currFolder.id)) !== null) {
            childFile = new File(dbFileObj.file_ID, dbFileObj.file_name, dbFileObj.file_path);
            currFolder.childFileList.push(childFile);
          }

      /* Process dbFolderList for child folder */
          while ((dbFolderObj = getChildFolder(dbFolderList, currFolder.id)) !== null) {
            processingQueue.push(dbFolderObj);
            childFolder = new Folder(dbFolderObj.folder_ID, dbFolderObj.folder_name, dbFolderObj.folder_path);
            childFolder.parentFolder = currFolder;
            currFolder.childFolderList.push(childFolder);
          }
        }
        return rootFolder;
      })
      .catch((error) => {
        throw error;
      }))
    .catch((error) => {
      throw error;
    });
  }

};

export default folderOperation;
<|endoftext|>


<commit_before>function package_path(object, path) {
    var items = path.split(".");
    var item = package;
    for (var part_index = 0; part_index < items.length; part_index++) {
        item = item[items[part_index]];
        if (!item) {
            throw path + " not found";
        }
    }
    return item;
}

function package_platform() {
    var platform = "browser";
    if (typeof require !== 'undefined') {
        platform = "server";
    } else if (typeof importScripts !== 'undefined') {
        platform = "client";
    }
    return platform;
}

function package_require(id, platform) {
    if (typeof platform !== "undefined") {
        package.requireComponents[id] = platform;
    }
    return package.requireComponents[id];
}

function package_remote(id, platform) {
    if (typeof platform !== "undefined") {
        package.remoteComponents[id] = platform;
    }
    return package.remoteComponents[id];
}

function package_init(package_name, component_name, child_name = null, node = null) {
    var children = [];
    /* Retrieve component function */
    var id = package_name + "." + component_name;
    if (!node) {
        node = package[id];
    }
    if (child_name) {
        node = node[child_name];
        id += "." + child_name;
    } else {
        /* Check if there are any child components */
        for (var key in node) {
            children.push(key);
        }
    }
    /* Register component in package */
    package[package_name].components.push(id);
    /* Create component proxy */
    var component_obj = new Proxy({id: id, package: package_name, component: component_name, child: child_name}, {
        get: function (object, property) {
            result = package_general(object, property);
            if (result) {
                return result;
            }
            return undefined;
        },
        set: function (object, property, value) {
            if (property !== "forward" && Reflect.has(object, "forward")) {
                var forward = Reflect.get(object, "forward");
                if (forward && forward.enabled && forward.set) {
                    forward.set(object, property, value);
                    return;
                }
            }
            Reflect.set(object, property, value);
        }
    });
    if (child_name) {
        Reflect.set(package[package_name + "." + component_name], child_name, component_obj);
    } else {
        Reflect.set(package[package_name], component_name, component_obj);
    }
    if (typeof node !== "function") {
        throw "Component " + id + " cannot be loaded stack: " + new Error().stack;
    }
    var requirement_platform = package.require(id);
    if (!requirement_platform || requirement_platform === package.platform) {
        node(component_obj, child_name);
        var init_method = component_obj.init;
        if (component_obj.forward) {
            component_obj.forward.enabled = true;
        }
        if (init_method) {
            package.initComponents.push(init_method);
        }
    }
    console.log(package.platform + ": Loaded " + component_obj.id);
    /* Load child components */
    children.map(function (child) {
        package_init(package_name, component_name, child, node);
    });
    return component_obj;
}

function package_prepare(package_name, component_name, callback) {
    var component = package_init(package_name, component_name);
    if (callback) {
        callback({loaded: {package: package_name, component: component_name}});
    }
    return component;
}

function package_load(package_name, component_name, callback) {
    var result = null;
    console.log(package.platform + ": Loading " + package_name + "." + component_name);
    if (package[package_name]) {
        var id = package_name + "." + component_name;
        package[package_name].components.map(function (name) {
            if (id !== name) {
                return;
            }
            result = package[id];
            if (callback) {
                callback({loaded: {package: package_name, component: component_name}});
            }
        });
    }
    if (result) {
        return result;
    }
    try {
        if (package.platform === "browser") {
            var ref = document.getElementsByTagName("script")[ 0 ];
            var script = document.createElement("script");
            script.src = "/packages/" + package_name + "/" + package_name + "_" + component_name + ".js?platform=browser";
            script.onload = function () {
                try {
                    package_prepare(package_name, component_name, callback);
                } catch (err) {
                    console.log("Found error: " + err + " stack: " + err.stack);
                    if (callback) {
                        callback({failure: {package: package_name, component: component_name}});
                    }
                }
            };
            ref.parentNode.insertBefore(script, ref);
        } else if (package.platform === "server") {
            path = "./" + package_name + "/" + package_name + "_" + component_name;
            require(path);
            result = package_prepare(package_name, component_name, callback);
        } else if (package.platform === "client") {
            importScripts("/packages/" + package_name + "/" + package_name + "_" + component_name + ".js?platform=client");
            result = package_prepare(package_name, component_name, callback);
        }
    } catch (err) {
        console.log("Found error: " + err + " stack: " + err.stack);
        if (callback) {
            callback({failure: {package: package_name, component: component_name}});
        }
    }
    return result;
}

function package_complete(info, callback) {
    do {
        var method = package.initComponents.shift();
        if(method) {
            method();
        }
    } while(method);
    if(info) {
        if (info.loaded) {
            info.complete = true;
        }
        if(callback) {
            callback(info);
        }
    }
}

function package_include(packages, callback) {
    if (typeof packages === "string" && packages) {
        var separator = packages.indexOf(".");
        var package_name = packages.substr(0, separator);
        var component_name = packages.substr(separator + 1);
        package_load(package_name, component_name, function (info) {
            package_complete(info, callback);
        });
        return;
    }
    var numComponents = 0;
    var loadedComponents = 0;
    var status = {};
    for (var package in packages) {
        packages[package].map(function (component) {
            status[package + "." + component] = false;
            numComponents++;
        });
    }
    var load = function (package_index, component_index) {
        var package_keys = Object.keys(packages);
        if (package_index >= package_keys.length) {
            return;
        }
        var package_name = package_keys[package_index];
        var components = packages[package_name];
        var component_name = components[component_index];
        package_load(package_name, component_name, function (info) {
            if (info.failure) {
                if (callback) {
                    callback(info);
                }
                return;
            }
            status[info.package + "." + info.component] = true;
            loadedComponents++;
            if (loadedComponents >= numComponents) {
                package_complete(info, callback);
                return;
            }
            info.progress = (loadedComponents / numComponents) * 100;
            if (callback) {
                callback(info);
            }
        });
        /* Load next component */
        component_index++;
        if (component_index >= components.length) {
            package_index++;
            component_index = 0;
        }
        setTimeout(function () {
            load(package_index, component_index);
        }, 0);
    };
    load(0, 0);
}

function package_general(object, property) {
    /* Check if package exists */
    if (Reflect.has(object, property)) {
        return Reflect.get(object, property);
    } else if (typeof property === "string" && property.includes(".")) {
        return package_path(object, property);
    } else if (property === "platform") {
        return package_platform();
    } else if (property === "require") {
        return package_require;
    } else if (property === "remote") {
        return package_remote;
    } else if (property === "include") {
        return package_include;
    } else if (property in package) {
        return package[property];
    } else if (property !== "forward" && Reflect.has(object, "forward")) {
        var forward = Reflect.get(object, "forward");
        if (forward && forward.enabled && forward.get) {
            return forward.get(object, property);
        }
    }
    return undefined;
}

var package = new Proxy({requireComponents: {}, remoteComponents: {}, initComponents:[]}, {
    get: function (object, property) {
        result = package_general(object, property);
        if (typeof result !== "undefined") {
            return result;
        }
        if (property === "id") {
            return "package";
        }
        /* Create package proxy */
        package_obj = new Proxy({id: property, package: property, components: []}, {
            get: function (object, property) {
                result = package_general(object, property);
                if (result) {
                    return result;
                }
                /* Load component */
                var package_name = Reflect.get(object, "id");
                Reflect.set(object, property, [""]);
                var result = package_load(package_name, property);
                package_complete(null, null);
                return result;
            },
            set: function (object, property, value) {
                Reflect.set(object, property, value);
            }
        });
        Reflect.set(object, property, package_obj);
        return package_obj;
    }
});

if (typeof require !== 'undefined') {
    global.package = package;
    global.__json__ = {};
}
<commit_msg>bug fixes<commit_after>function package_path(object, path) {
    var items = path.split(".");
    var item = package;
    for (var part_index = 0; part_index < items.length; part_index++) {
        item = item[items[part_index]];
        if (!item) {
            throw path + " not found";
        }
    }
    return item;
}

function package_platform() {
    var platform = "browser";
    if (typeof require !== 'undefined') {
        platform = "server";
    } else if (typeof importScripts !== 'undefined') {
        platform = "client";
    }
    return platform;
}

function package_require(id, platform) {
    if (typeof platform !== "undefined") {
        package.requireComponents[id] = platform;
    }
    return package.requireComponents[id];
}

function package_remote(id, platform) {
    if (typeof platform !== "undefined") {
        package.remoteComponents[id] = platform;
    }
    return package.remoteComponents[id];
}

function package_init(package_name, component_name, callback, child_name = null, node = null) {
    var children = [];
    /* Retrieve component function */
    var id = package_name + "." + component_name;
    if (!node) {
        node = package[id];
    }
    if (child_name) {
        node = node[child_name];
        id += "." + child_name;
    } else {
        /* Check if there are any child components */
        for (var key in node) {
            children.push(key);
        }
    }
    /* Register component in package */
    package[package_name].components.push(id);
    /* Create component proxy */
    var component_obj = new Proxy({id: id, package: package_name, component: component_name, child: child_name}, {
        get: function (object, property) {
            result = package_general(object, property);
            if (result) {
                return result;
            }
            return undefined;
        },
        set: function (object, property, value) {
            if (property !== "forward" && Reflect.has(object, "forward")) {
                var forward = Reflect.get(object, "forward");
                if (forward && forward.enabled && forward.set) {
                    forward.set(object, property, value);
                    return;
                }
            }
            Reflect.set(object, property, value);
        }
    });
    if (child_name) {
        Reflect.set(package[package_name + "." + component_name], child_name, component_obj);
    } else {
        Reflect.set(package[package_name], component_name, component_obj);
    }
    if (typeof node !== "function") {
        throw "Component " + id + " cannot be loaded stack: " + new Error().stack;
    }
    var requirement_platform = package.require(id);
    if (!requirement_platform || requirement_platform === package.platform) {
        node(component_obj, child_name);
        var init_method = component_obj.init;
        if (component_obj.forward) {
            component_obj.forward.enabled = true;
        }
        if (init_method) {
            if(callback) {
                package.initComponents.push(init_method);
            }
            else {
                init_method();
            }
        }
    }
    console.log(package.platform + ": Loaded " + component_obj.id);
    /* Load child components */
    children.map(function (child) {
        package_init(package_name, component_name, callback, child, node);
    });
    return component_obj;
}

function package_prepare(package_name, component_name, callback) {
    var component = package_init(package_name, component_name, callback);
    if (callback) {
        callback({loaded: {package: package_name, component: component_name}});
    }
    return component;
}

function package_load(package_name, component_name, callback) {
    var result = null;
    console.log(package.platform + ": Loading " + package_name + "." + component_name);
    if (package[package_name]) {
        var id = package_name + "." + component_name;
        package[package_name].components.map(function (name) {
            if (id !== name) {
                return;
            }
            result = package[id];
            if (callback) {
                callback({loaded: {package: package_name, component: component_name}});
            }
        });
    }
    if (result) {
        return result;
    }
    try {
        if (package.platform === "browser") {
            var ref = document.getElementsByTagName("script")[ 0 ];
            var script = document.createElement("script");
            script.src = "/packages/" + package_name + "/" + package_name + "_" + component_name + ".js?platform=browser";
            script.onload = function () {
                try {
                    package_prepare(package_name, component_name, callback);
                } catch (err) {
                    console.log("Found error: " + err + " stack: " + err.stack);
                    if (callback) {
                        callback({failure: {package: package_name, component: component_name}});
                    }
                }
            };
            ref.parentNode.insertBefore(script, ref);
        } else if (package.platform === "server") {
            path = "./" + package_name + "/" + package_name + "_" + component_name;
            require(path);
            result = package_prepare(package_name, component_name, callback);
        } else if (package.platform === "client") {
            importScripts("/packages/" + package_name + "/" + package_name + "_" + component_name + ".js?platform=client");
            result = package_prepare(package_name, component_name, callback);
        }
    } catch (err) {
        console.log("Found error: " + err + " stack: " + err.stack);
        if (callback) {
            callback({failure: {package: package_name, component: component_name}});
        }
    }
    return result;
}

function package_complete(info, callback) {
    do {
        var method = package.initComponents.shift();
        if(method) {
            method();
        }
    } while(method);
    if(info) {
        if (info.loaded) {
            info.complete = true;
        }
        if(callback) {
            callback(info);
        }
    }
}

function package_include(packages, callback) {
    if (typeof packages === "string" && packages) {
        var separator = packages.indexOf(".");
        var package_name = packages.substr(0, separator);
        var component_name = packages.substr(separator + 1);
        package_load(package_name, component_name, function (info) {
            package_complete(info, callback);
        });
        return;
    }
    var numComponents = 0;
    var loadedComponents = 0;
    var status = {};
    for (var package in packages) {
        packages[package].map(function (component) {
            status[package + "." + component] = false;
            numComponents++;
        });
    }
    var load = function (package_index, component_index) {
        var package_keys = Object.keys(packages);
        if (package_index >= package_keys.length) {
            return;
        }
        var package_name = package_keys[package_index];
        var components = packages[package_name];
        var component_name = components[component_index];
        package_load(package_name, component_name, function (info) {
            if (info.failure) {
                if (callback) {
                    callback(info);
                }
                return;
            }
            status[info.package + "." + info.component] = true;
            loadedComponents++;
            if (loadedComponents >= numComponents) {
                package_complete(info, callback);
                return;
            }
            info.progress = (loadedComponents / numComponents) * 100;
            if (callback) {
                callback(info);
            }
        });
        /* Load next component */
        component_index++;
        if (component_index >= components.length) {
            package_index++;
            component_index = 0;
        }
        setTimeout(function () {
            load(package_index, component_index);
        }, 0);
    };
    load(0, 0);
}

function package_general(object, property) {
    /* Check if package exists */
    if (Reflect.has(object, property)) {
        return Reflect.get(object, property);
    } else if (typeof property === "string" && property.includes(".")) {
        return package_path(object, property);
    } else if (property === "platform") {
        return package_platform();
    } else if (property === "require") {
        return package_require;
    } else if (property === "remote") {
        return package_remote;
    } else if (property === "include") {
        return package_include;
    } else if (property in package) {
        return package[property];
    } else if (property !== "forward" && Reflect.has(object, "forward")) {
        var forward = Reflect.get(object, "forward");
        if (forward && forward.enabled && forward.get) {
            return forward.get(object, property);
        }
    }
    return undefined;
}

var package = new Proxy({requireComponents: {}, remoteComponents: {}, initComponents:[]}, {
    get: function (object, property) {
        result = package_general(object, property);
        if (typeof result !== "undefined") {
            return result;
        }
        if (property === "id") {
            return "package";
        }
        /* Create package proxy */
        package_obj = new Proxy({id: property, package: property, components: []}, {
            get: function (object, property) {
                result = package_general(object, property);
                if (result) {
                    return result;
                }
                /* Load component */
                var package_name = Reflect.get(object, "id");
                Reflect.set(object, property, [""]);
                var result = package_load(package_name, property);
                return result;
            },
            set: function (object, property, value) {
                Reflect.set(object, property, value);
            }
        });
        Reflect.set(object, property, package_obj);
        return package_obj;
    }
});

if (typeof require !== 'undefined') {
    global.package = package;
    global.__json__ = {};
}
<|endoftext|>


<commit_before>jsio('import std.js as JS');

var attrs = [
	"source",
	"protocol",
	"authority",
	"userInfo",
	"user",
	"password",
	"host",
	"port",
	"relative",
	"path",
	"directory",
	"file",
	"query",
	"anchor"
];

var URI = exports = Class(function(supr) {
	this.init = function(url, isStrict) {
		if (url instanceof URI) {
			for (var i = 0, attr; attr = attrs[i]; ++i) {
				this['_' + attr] = url['_' + attr];
			}
			return;
		}

		this._isStrict = isStrict;

		var uriData = exports.parse(url, isStrict);
		for (var attr in uriData) {
			this['_' + attr] = uriData[attr];
		};
	}

	for (var i = 0, attr; attr = attrs[i]; ++i) {
		(function(attr) {
			var fNameSuffix = attr.charAt(0).toUpperCase() + attr.slice(1);
			this['get' + fNameSuffix] = function() {
				return this['_' + attr];
			};
			this['set' + fNameSuffix] = function(val) {
				this['_' + attr] = val;
			};
		}).call(this, attr);
	};

	this.query = function(key) { return exports.parseQuery(this._query)[key]; }
	this.hash = function(key) { return exports.parseQuery(this._hash)[key]; }

	this.addQuery = function(kvp) {
		this._query = exports.buildQuery(JS.merge(kvp, exports.parseQuery(this._query)));
		return this;
	}

	this.toString = this.render = function(onlyBase) {
		// XXX TODO: This is vaguely reasonable, but not complete. fix it...
		var a = this._protocol ? this._protocol + "://" : ""
		var b = this._host ? this._host + ((this._port || 80) == 80 ? "" : ":" + this._port) : "";

		if (onlyBase) {
			return a + b;
		}

		var c = this._path;
		var d = this._query ? '?' + this._query : '';
		var e = this._anchor ? '#' + this._anchor : '';
		return a + b + c + d + e;
	};
});

exports.relativeTo = function(url, base) {
	url = String(url);

	if (/^http(s?):\/\//.test(url)) { return url; }
	if (url.charAt(0) == '/') {
		var baseuri = new exports(base);
		url = baseuri.toString(true) + url;
	} else {
		url = base + url;
	}

	return exports.resolveRelative(url);
}

exports.resolveRelative = function(url) {
	var prevUrl;

	// remove ../ with preceeding folder
	while((prevUrl = url) != (url = url.replace(/(^|\/)([^\/]+)\/\.\.\//g, '/'))) {};

	// remove ./ if it isn't preceeded by a .
	return url.replace(/[^.]\.\//g, '');
}

exports.buildQuery = function(kvp) {
	var pairs = [];
	for (var key in kvp) {
		pairs.push(encodeURIComponent(key) + '=' + encodeURIComponent(kvp[key]));
	}
	return pairs.join('&');
}

exports.parseQuery = function(str) {
	var pairs = str.split('&'),
		n = pairs.length,
		data = {};
	for (var i = 0; i < n; ++i) {
		var pair = pairs[i].split('='),
			key = decodeURIComponent(pair[0]);
		if (key) { data[key] = decodeURIComponent(pair[1]); }
	}
	return data;
}

// Regexs are based on parseUri 1.2.2
// Original: (c) Steven Levithan <stevenlevithan.com>
// Original: MIT License

var strictRegex = /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/;
var looseRegex = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/;
var queryStringRegex = /(?:^|&)([^&=]*)=?([^&]*)/g;

exports.parse = function(str, isStrict) {
	var regex = isStrict ? strictRegex : looseRegex;
	var result = {};
	var match = regex.exec(str);
	for (var i = 0, attr; attr = attrs[i]; ++i) {
		result[attr] = match[i] || "";
	}

	var qs = result['queryKey'] = {};
	result['query'].replace(queryStringRegex, function(check, key, val) {
		if (check) {
			qs[key] = val;
		}
	});

	return result;
}

exports.isSameDomain = function(urlA, urlB) {
	var a = exports.parse(urlA);
	var b = exports.parse(urlB);
	return ((a.port == b.port ) && (a.host == b.host) && (a.protocol == b.protocol));
};
<commit_msg>uri fixes<commit_after>var attrs = [
	"source",
	"protocol",
	"authority",
	"userInfo",
	"user",
	"password",
	"host",
	"port",
	"relative",
	"path",
	"directory",
	"file",
	"query",
	"anchor"
];

var URI = exports = Class(function(supr) {
	this.init = function(url, isStrict) {
		if (url instanceof URI) {
			for (var i = 0, attr; attr = attrs[i]; ++i) {
				this['_' + attr] = url['_' + attr];
			}
			return;
		}

		this._isStrict = isStrict;

		var uriData = exports.parse(url, isStrict);
		for (var attr in uriData) {
			this['_' + attr] = uriData[attr];
		};
	}

	for (var i = 0, attr; attr = attrs[i]; ++i) {
		(function(attr) {
			var fNameSuffix = attr.charAt(0).toUpperCase() + attr.slice(1);
			this['get' + fNameSuffix] = function() {
				return this['_' + attr];
			};
			this['set' + fNameSuffix] = function(val) {
				this['_' + attr] = val;
			};
		}).call(this, attr);
	};

	this.query = function(key) { return exports.parseQuery(this._query)[key]; }
	this.hash = function(key) { return exports.parseQuery(this._anchor)[key]; }

	this.addHash = function(kvp) {
		var hash = exports.parseQuery(this._anchor);
		for (var i in kvp) { hash[i] = kvp[i]; }
		this._anchor = exports.buildQuery(hash);
		return this;
	}

	this.addQuery = function(kvp) {
		var query = exports.parseQuery(this._query);
		for (var i in kvp) { query[i] = kvp[i]; }
		this._query = exports.buildQuery(query);
		return this;
	}

	this.removeQuery = function(keys) {
		var query = exports.parseQuery(this._query);
		if (isArray(keys)) {
			for (var i = 0, n = keys.length; i < n; ++i) {
				delete query[keys[i]];
			}
		} else {
			delete query[keys];
		}
		this._query = exports.buildQuery(query);
		return this;
	}

	this.toString = this.render = function(onlyBase) {
		// XXX TODO: This is vaguely reasonable, but not complete. fix it...
		var a = this._protocol ? this._protocol + "://" : ""
		var b = this._host ? this._host + ((this._port || 80) == 80 ? "" : ":" + this._port) : "";

		if (onlyBase) {
			return a + b;
		}

		var c = this._path;
		var d = this._query ? '?' + this._query : '';
		var e = this._anchor ? '#' + this._anchor : '';
		return a + b + c + d + e;
	};
});

exports.relativeTo = function(url, base) {
	url = String(url);

	if (/^http(s?):\/\//.test(url)) { return url; }
	if (url.charAt(0) == '/') {
		var baseuri = new exports(base);
		url = baseuri.toString(true) + url;
	} else {
		url = base + url;
	}

	return exports.resolveRelative(url);
}

exports.resolveRelative = function(url) {
	var prevUrl;

	// remove ../ with preceeding folder
	while((prevUrl = url) != (url = url.replace(/(^|\/)([^\/]+)\/\.\.\//g, '/'))) {};

	// remove ./ if it isn't preceeded by a .
	return url.replace(/[^.]\.\//g, '');
}

exports.buildQuery = function(kvp) {
	var pairs = [];
	for (var key in kvp) {
		pairs.push(encodeURIComponent(key) + '=' + encodeURIComponent(kvp[key]));
	}
	return pairs.join('&');
}

exports.parseQuery = function(str) {
	var pairs = str.split('&'),
		n = pairs.length,
		data = {};
	for (var i = 0; i < n; ++i) {
		var pair = pairs[i].split('='),
			key = decodeURIComponent(pair[0]);
		if (key) { data[key] = decodeURIComponent(pair[1]); }
	}
	return data;
}

// Regexs are based on parseUri 1.2.2
// Original: (c) Steven Levithan <stevenlevithan.com>
// Original: MIT License

var strictRegex = /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/;
var looseRegex = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/;
var queryStringRegex = /(?:^|&)([^&=]*)=?([^&]*)/g;

exports.parse = function(str, isStrict) {
	var regex = isStrict ? strictRegex : looseRegex;
	var result = {};
	var match = regex.exec(str);
	for (var i = 0, attr; attr = attrs[i]; ++i) {
		result[attr] = match[i] || "";
	}

	var qs = result['queryKey'] = {};
	result['query'].replace(queryStringRegex, function(check, key, val) {
		if (check) {
			qs[key] = val;
		}
	});

	return result;
}

exports.isSameDomain = function(urlA, urlB) {
	var a = exports.parse(urlA);
	var b = exports.parse(urlB);
	return ((a.port == b.port ) && (a.host == b.host) && (a.protocol == b.protocol));
};
<|endoftext|>