<commit_before>// "region" ReaderSetup dialog
function CreateConfigDiv(title) {

    var dialogContents = $('
').appendTo($("body"));

    var html = '';

    dialogContents.append(html);

    return dialogContents;
}

function showSetupDialog(showWhat) {

    var accordion = document.getElementById('accordion').contentWindow;
    accordion.localizationManager.loadStrings(getSettingsDialogLocalizedStrings(), null, function() {

        var title;
        if (showWhat == 'stages')
            title = accordion.localizationManager.getText('ReaderSetup.SetUpDecodableReaderTool', 'Set up Decodable Reader Tool');
        else
            title = accordion.localizationManager.getText('ReaderSetup.SetUpLeveledReaderTool', 'Set up Leveled Reader Tool');

        var dialogContents = CreateConfigDiv(title);

        accordion.model.setupType = showWhat;

        $(dialogContents).dialog({
            autoOpen: "true",
            modal: "true",
            buttons: {
                Help: {
                    // For consistency, I would have made this 'Common.Help', but we already had 'HelpMenu.Help Menu' translated
                    text: accordion.localizationManager.getText('HelpMenu.Help Menu', 'Help'),
                    class: 'left-button',
                    click: function() {
                        document.getElementById('settings_frame').contentWindow.postMessage('Help', '*');
                    }
                },
                OK: {
                    text: accordion.localizationManager.getText('Common.OK', 'OK'),
                    click: function () {
                        document.getElementById('settings_frame').contentWindow.postMessage('OK', '*');
                    }
                },

                Cancel: {
                    text: accordion.localizationManager.getText('Common.Cancel', 'Cancel'),
                    click: function () {
                        $(this).dialog("close");
                    }
                }
            },
            close: function() {
                $(this).remove();
               fireCSharpEvent('setModalStateEvent', 'false');
            },
            open: function () {
                $('#synphonyConfig').css('overflow', 'hidden');
                $('button span:contains("Help")').prepend(' ');
            }
//            height: 0,
//            width: 700
        });

        fireCSharpEvent('setModalStateEvent', 'true');
    });
}

function getSettingsDialogLocalizedStrings() {
    // Without preloading these, they are not available when the dialog is created
    var pairs = {};
    pairs['ReaderSetup.SetUpDecodableReaderTool'] = 'Set up Decodable Reader Tool';
    pairs['ReaderSetup.SetUpLeveledReaderTool'] = 'Set up Leveled Reader Tool';
    pairs['HelpMenu.Help Menu'] = 'Help';
    pairs['Common.OK'] = 'OK';
    pairs['Common.Cancel'] = 'Cancel';
    return pairs;
}

//noinspection JSUnusedGlobalSymbols
/**
 * Used by the settings_frame to initialize the setup dialog
 */
function initializeReaderSetupDialog() {

    var model = document.getElementById('accordion').contentWindow.model;

    var sourceMsg = 'Data\n' +  JSON.stringify(model.getSynphony().source);
    var fontMsg = 'Font\n' +  model.fontName;
    document.getElementById('settings_frame').contentWindow.postMessage(sourceMsg, '*');
    document.getElementById('settings_frame').contentWindow.postMessage(fontMsg, '*');
}

/**
 * Called by C# after the setup data has been saved, following Save click.
 */
function closeSetupDialog() {
    $('#synphonyConfig').dialog("close");
}
// "endregion" ReaderSetup dialog

// "region" Add Page dialog
function CreateAddPageDiv(templatesJSON, descriptionLabel, blankPreviewMsg) {

    var dialogContents = $('
').appendTo($('body'));

    var html = "";

    dialogContents.append(html);

    // When the page chooser loads, get the iframe holding it to resize to what's inside
    $('#addPage_frame').load(function() {
        localizeDialogContents(dialogContents, descriptionLabel, blankPreviewMsg);
        initializeAddPageDialog(templatesJSON);
    });

    return dialogContents;
}

//noinspection JSUnusedGlobalSymbols
// method called from EditingModel.cs
// for 'templatesJSON', see property EditingModel.GetJsonTemplatePageObject
function showAddPageDialog(templatesJSON) {

    var theDialog;
    var parentElement = document.getElementById('page').contentWindow;

    parentElement.localizationManager.loadStrings(getAddPageDialogLocalizedStrings(), null, function() {

        var title = parentElement.localizationManager.getText('AddPageDialog.Title', 'Add Page...');
        var addButtonText = parentElement.localizationManager.getText('AddPageDialog.AddPageButton', 'Add This Page');
        var descriptionLabel = parentElement.localizationManager.getText('AddPageDialog.DescriptionLabel', 'Description');
        var blankPreviewMsg = parentElement.localizationManager.getText('AddPageDialog.PreviewMessage',
            'This will contain a preview of a template page when one is selected.');
        var dialogContents = CreateAddPageDiv(templatesJSON, descriptionLabel, blankPreviewMsg);

        theDialog = $(dialogContents).dialog({
            class: "addPageDialog",
            autoOpen: false,
            resizable: false,
            modal: true,
            width: 795,
            height: 550,
            position: {
                my: "left bottom", at: "left bottom", of: window
            },
            title: title,
            /*buttons: {
                OK: {
                    class: 'defaultButton',
                    text: addButtonText,
//                    we could reinstate this if we had the right color
//                    icons: {
//                        primary: "ui-icon-plusthick"
//                    },
                    click: function () {
                        fireCSharpEvent('setModalStateEvent', 'false');
                        document.getElementById('addPage_frame').contentWindow.postMessage('AddSelectedPage', '*');
                    }
                },
                Cancel: {
                    class: 'normalButton',
                    text: parentElement.localizationManager.getText('Common.Cancel', 'Cancel'),
                    click: function () {
                        $(this).dialog("close");
                    }
                }
            },*/
            close: function() {
                $(this).remove();
                fireCSharpEvent('setModalStateEvent', 'false');
            },
            open: function () {
                //adjustAddPageButton(addButtonText);
                setTimeout(function() {
                    setDialogInnerIframeSize();

                    jQuery('#addPageButton').click(function () {
                            alert("addpageclick");
                            fireCSharpEvent('setModalStateEvent', 'false');
                            document.getElementById('addPage_frame').contentWindow.postMessage('AddSelectedPage', '*');
                            return false;
                    });

          //          jQuery.notify("testing notify");
                }, 200);
            }
        });

        //TODO:  this doesn't work yet. We need to make it work, and then make it localizationManager.asyncGetText(...).done(translation => { do the insertion into the dialog });
        // theDialog.find('.ui-dialog-buttonpane').prepend("
You can press ctrl+N to add the same page again, without opening this dialog.
");

        jQuery(document).on('click', 'body > .ui-widget-overlay', function () {
            $(".ui-dialog-titlebar-close").trigger('click');
            return false;
        });
        fireCSharpEvent('setModalStateEvent', 'true');
        theDialog.dialog('open');

        //parentElement.$.notify("testing notify",{});
    });
}

//function adjustAddPageButton(addButtonText) {
//    var iconAdjustment = 15;
//    var button = $('.ui-dialog-buttonpane').find('button:contains('+addButtonText+')');
//    button.width(button.width() + iconAdjustment); // make room for the 'plus' icon on the button
//}

function setDialogInnerIframeSize() {
    var $frame = $('#addPage_frame');
    var width = $frame.contents().find('#mainContainer').width();
    var height = $frame.contents().find('#mainContainer').height();
    $frame.height(height);
    $frame.width(width);
}

function localizeDialogContents(dialogContents, description, blankMessage) {
    $(dialogContents).find('iframe').contents().find('.DescriptionHeader').text(description);
    $(dialogContents).find('iframe').contents().find('iframe').contents().find('#innerBox').text(blankMessage);
}

function getAddPageDialogLocalizedStrings() {
    // Without preloading these, they are not available when the dialog is created
    var pairs = {};
    pairs['AddPageDialog.Title'] = 'Add Page...';
    pairs['AddPageDialog.DescriptionLabel'] = 'Description';
    pairs['AddPageDialog.PreviewMessage'] = 'This will contain a preview of a template page when one is selected.';
    //pairs['HelpMenu.Help Menu'] = 'Help';
    pairs['AddPageDialog.AddPageButton'] = 'Add This Page';
    pairs['Common.Cancel'] = 'Cancel';
    return pairs;
}

//noinspection JSUnusedGlobalSymbols
// Used by the addPage_frame to initialize the setup dialog with the available template pages
// 'templatesJSON' will be something like:
//([{ "templateBookFolderUrl": "/bloom/localhost//...(path to files).../factoryCollections/Templates/Basic Book/",
//      "templateBookUrl": "/bloom/localhost/...(path to files).../factoryCollections/Templates/Basic Book/Basic Book.htm" }])
// See property EditingModel.GetJsonTemplatePageObject
function initializeAddPageDialog(templatesJSON) {
    var templateMsg = 'Data\n' + JSON.stringify(templatesJSON);
    document.getElementById('addPage_frame').contentWindow.postMessage(templateMsg, '*');
}
// "endregion" Add Page dialog

/**
 * Fires an event for C# to handle
 * @param {String} eventName
 * @param {String} eventData
 */
// Enhance: JT notes that this method pops up from time to time; can we consolidate?
function fireCSharpEvent(eventName, eventData) {

    var event = new MessageEvent(eventName, {'view' : window, 'bubbles' : true, 'cancelable' : true, 'data' : eventData});
    document.dispatchEvent(event);
    // For when we someday change this file to TypeScript... since the above ctor is not declared anywhere.
    // Solution III (works)
    //var event = new (<any>MessageEvent)(eventName, { 'view': window, 'bubbles': true, 'cancelable': true, 'data': eventData });
}<commit_msg>Fix Reader Tools Setup dialog messed up by earlier merge<commit_after>// "region" ReaderSetup dialog
function CreateConfigDiv(title) {

    var dialogContents = $('
').appendTo($("body"));

    var html = '';

    dialogContents.append(html);

    return dialogContents;
}

function showSetupDialog(showWhat) {

    var accordion = document.getElementById('accordion').contentWindow;
    accordion.localizationManager.loadStrings(getSettingsDialogLocalizedStrings(), null, function() {

        var title;
        if (showWhat == 'stages')
            title = accordion.localizationManager.getText('ReaderSetup.SetUpDecodableReaderTool', 'Set up Decodable Reader Tool');
        else
            title = accordion.localizationManager.getText('ReaderSetup.SetUpLeveledReaderTool', 'Set up Leveled Reader Tool');

        var dialogContents = CreateConfigDiv(title);

        var h = 580;
        var w = 720;
        var size = getAppropriateDialogSize(h, w);
        h = size[0];
        w = size[1];

        accordion.model.setupType = showWhat;

        $(dialogContents).dialog({
            autoOpen: "true",
            modal: "true",
            buttons: {
                Help: {
                    // For consistency, I would have made this 'Common.Help', but we already had 'HelpMenu.Help Menu' translated
                    text: accordion.localizationManager.getText('HelpMenu.Help Menu', 'Help'),
                    class: 'left-button',
                    click: function() {
                        document.getElementById('settings_frame').contentWindow.postMessage('Help', '*');
                    }
                },
                OK: {
                    text: accordion.localizationManager.getText('Common.OK', 'OK'),
                    click: function () {
                        document.getElementById('settings_frame').contentWindow.postMessage('OK', '*');
                    }
                },

                Cancel: {
                    text: accordion.localizationManager.getText('Common.Cancel', 'Cancel'),
                    click: function () {
                        $(this).dialog("close");
                    }
                }
            },
            close: function() {
                $(this).remove();
               fireCSharpEvent('setModalStateEvent', 'false');
            },
            open: function () {
                $('#synphonyConfig').css('overflow', 'hidden');
                $('button span:contains("Help")').prepend(' ');
            },
            height: h,
            width: w
        });

        fireCSharpEvent('setModalStateEvent', 'true');
    });
}

function getAppropriateDialogSize(preferredHeight, preferredWidth) {
    var h = preferredHeight;
    var w = preferredWidth;

    // This height and width will fit inside the "800 x 600" settings
    var sw = document.body.scrollWidth;
    if (sw < 583) {
        h = 460;
        w = 390;
    }

    // This height and width will fit inside the "1024 x 586 Low-end netbook with windows Task bar" settings
    else if ((sw < 723) || (window.innerHeight < 583)) {
        h = 460;
        w = 580;
    }

    return [h, w];
}

function getSettingsDialogLocalizedStrings() {
    // Without preloading these, they are not available when the dialog is created
    var pairs = {};
    pairs['ReaderSetup.SetUpDecodableReaderTool'] = 'Set up Decodable Reader Tool';
    pairs['ReaderSetup.SetUpLeveledReaderTool'] = 'Set up Leveled Reader Tool';
    pairs['HelpMenu.Help Menu'] = 'Help';
    pairs['Common.OK'] = 'OK';
    pairs['Common.Cancel'] = 'Cancel';
    return pairs;
}

//noinspection JSUnusedGlobalSymbols
/**
 * Used by the settings_frame to initialize the setup dialog
 */
function initializeReaderSetupDialog() {

    var model = document.getElementById('accordion').contentWindow.model;

    var sourceMsg = 'Data\n' +  JSON.stringify(model.getSynphony().source);
    var fontMsg = 'Font\n' +  model.fontName;
    document.getElementById('settings_frame').contentWindow.postMessage(sourceMsg, '*');
    document.getElementById('settings_frame').contentWindow.postMessage(fontMsg, '*');
}

/**
 * Called by C# after the setup data has been saved, following Save click.
 */
function closeSetupDialog() {
    $('#synphonyConfig').dialog("close");
}
// "endregion" ReaderSetup dialog

// "region" Add Page dialog
function CreateAddPageDiv(templatesJSON, descriptionLabel, blankPreviewMsg) {

    var dialogContents = $('
').appendTo($('body'));

    var html = "";

    dialogContents.append(html);

    // When the page chooser loads, get the iframe holding it to resize to what's inside
    $('#addPage_frame').load(function() {
        localizeDialogContents(dialogContents, descriptionLabel, blankPreviewMsg);
        initializeAddPageDialog(templatesJSON);
    });

    return dialogContents;
}

//noinspection JSUnusedGlobalSymbols
// method called from EditingModel.cs
// for 'templatesJSON', see property EditingModel.GetJsonTemplatePageObject
function showAddPageDialog(templatesJSON) {

    var theDialog;
    var parentElement = document.getElementById('page').contentWindow;

    parentElement.localizationManager.loadStrings(getAddPageDialogLocalizedStrings(), null, function() {

        var title = parentElement.localizationManager.getText('AddPageDialog.Title', 'Add Page...');
        var addButtonText = parentElement.localizationManager.getText('AddPageDialog.AddPageButton', 'Add This Page');
        var descriptionLabel = parentElement.localizationManager.getText('AddPageDialog.DescriptionLabel', 'Description');
        var blankPreviewMsg = parentElement.localizationManager.getText('AddPageDialog.PreviewMessage',
            'This will contain a preview of a template page when one is selected.');
        var dialogContents = CreateAddPageDiv(templatesJSON, descriptionLabel, blankPreviewMsg);

        theDialog = $(dialogContents).dialog({
            class: "addPageDialog",
            autoOpen: false,
            resizable: false,
            modal: true,
            width: 795,
            height: 550,
            position: {
                my: "left bottom", at: "left bottom", of: window
            },
            title: title,
            /*buttons: {
                OK: {
                    class: 'defaultButton',
                    text: addButtonText,
//                    we could reinstate this if we had the right color
//                    icons: {
//                        primary: "ui-icon-plusthick"
//                    },
                    click: function () {
                        fireCSharpEvent('setModalStateEvent', 'false');
                        document.getElementById('addPage_frame').contentWindow.postMessage('AddSelectedPage', '*');
                    }
                },
                Cancel: {
                    class: 'normalButton',
                    text: parentElement.localizationManager.getText('Common.Cancel', 'Cancel'),
                    click: function () {
                        $(this).dialog("close");
                    }
                }
            },*/
            close: function() {
                $(this).remove();
                fireCSharpEvent('setModalStateEvent', 'false');
            },
            open: function () {
                //adjustAddPageButton(addButtonText);
                setTimeout(function() {
                    setDialogInnerIframeSize();

                    jQuery('#addPageButton').click(function () {
                            alert("addpageclick");
                            fireCSharpEvent('setModalStateEvent', 'false');
                            document.getElementById('addPage_frame').contentWindow.postMessage('AddSelectedPage', '*');
                            return false;
                    });

          //          jQuery.notify("testing notify");
                }, 200);
            }
        });

        //TODO:  this doesn't work yet. We need to make it work, and then make it localizationManager.asyncGetText(...).done(translation => { do the insertion into the dialog });
        // theDialog.find('.ui-dialog-buttonpane').prepend("
You can press ctrl+N to add the same page again, without opening this dialog.
");

        jQuery(document).on('click', 'body > .ui-widget-overlay', function () {
            $(".ui-dialog-titlebar-close").trigger('click');
            return false;
        });
        fireCSharpEvent('setModalStateEvent', 'true');
        theDialog.dialog('open');

        //parentElement.$.notify("testing notify",{});
    });
}

//function adjustAddPageButton(addButtonText) {
//    var iconAdjustment = 15;
//    var button = $('.ui-dialog-buttonpane').find('button:contains('+addButtonText+')');
//    button.width(button.width() + iconAdjustment); // make room for the 'plus' icon on the button
//}

function setDialogInnerIframeSize() {
    var $frame = $('#addPage_frame');
    var width = $frame.contents().find('#mainContainer').width();
    var height = $frame.contents().find('#mainContainer').height();
    $frame.height(height);
    $frame.width(width);
}

function localizeDialogContents(dialogContents, description, blankMessage) {
    $(dialogContents).find('iframe').contents().find('.DescriptionHeader').text(description);
    $(dialogContents).find('iframe').contents().find('iframe').contents().find('#innerBox').text(blankMessage);
}

function getAddPageDialogLocalizedStrings() {
    // Without preloading these, they are not available when the dialog is created
    var pairs = {};
    pairs['AddPageDialog.Title'] = 'Add Page...';
    pairs['AddPageDialog.DescriptionLabel'] = 'Description';
    pairs['AddPageDialog.PreviewMessage'] = 'This will contain a preview of a template page when one is selected.';
    //pairs['HelpMenu.Help Menu'] = 'Help';
    pairs['AddPageDialog.AddPageButton'] = 'Add This Page';
    pairs['Common.Cancel'] = 'Cancel';
    return pairs;
}

//noinspection JSUnusedGlobalSymbols
// Used by the addPage_frame to initialize the setup dialog with the available template pages
// 'templatesJSON' will be something like:
//([{ "templateBookFolderUrl": "/bloom/localhost//...(path to files).../factoryCollections/Templates/Basic Book/",
//      "templateBookUrl": "/bloom/localhost/...(path to files).../factoryCollections/Templates/Basic Book/Basic Book.htm" }])
// See property EditingModel.GetJsonTemplatePageObject
function initializeAddPageDialog(templatesJSON) {
    var templateMsg = 'Data\n' + JSON.stringify(templatesJSON);
    document.getElementById('addPage_frame').contentWindow.postMessage(templateMsg, '*');
}
// "endregion" Add Page dialog

/**
 * Fires an event for C# to handle
 * @param {String} eventName
 * @param {String} eventData
 */
// Enhance: JT notes that this method pops up from time to time; can we consolidate?
function fireCSharpEvent(eventName, eventData) {

    var event = new MessageEvent(eventName, {'view' : window, 'bubbles' : true, 'cancelable' : true, 'data' : eventData});
    document.dispatchEvent(event);
    // For when we someday change this file to TypeScript... since the above ctor is not declared anywhere.
    // Solution III (works)
    //var event = new (<any>MessageEvent)(eventName, { 'view': window, 'bubbles': true, 'cancelable': true, 'data': eventData });
}<|endoftext|>


<commit_before>import { CursorLength, Width, Height, Size, WidthLevel, HeightLevel } from '../Constants.js';

const LengthAnimation = 50;
const MaxLayer = 3;

class MapManager {

  constructor(map, lastLayerAvailable) {
    this.removedBlock = [];
    this.cacheCollisionLayer = [];
    this.lastLayerAvailable = lastLayerAvailable;
    this.map = map;
    this.nbGems = 0;
    this.currentGems = 0;
    this.doorSprites = [];
    this.visibleDoor = false;
  }

  shouldPicked(tile) {
    return tile.properties.is_gem == 1 || tile.properties.layer_gem == 1;
  }

  findLayerToDestroy(x, y, lengthX, lengthY) {
    //layer can be deleted 3 and 2
    let layerIndex = MaxLayer;
    for(let index = MaxLayer; index > 1; index--) {
      if( this.map.getTile(x, y, index) === null &&
          this.map.getTile(x + lengthX-1, y + lengthY-1, index) === null) {
        layerIndex--;
      } else {
        break;
      }
    }
    //impossibe
    if(layerIndex <= 1 ||  layerIndex <= this.lastLayerAvailable) {
      return -1;
    }
    return layerIndex;
  }

  setUpCollisionLayer(colissionLayer) {
    colissionLayer.layer.data.forEach(column => {
      column.forEach(tile => {
        if(tile.properties.layer_index && tile.properties.layer_index != MaxLayer) {
          this.cacheCollisionLayer.push(tile);
          this.map.removeTile(tile.x, tile.y, "colissionLayer");
         }

         if(tile.properties.layer_index) {
          tile.alpha = 0;
         }

        if(this.shouldPicked(tile)) {
          if(tile.properties.layer_index == MaxLayer) {
            tile.alpha = 1;
          } else {
            tile.alpha = 0;
          }
        }

        if(tile.properties.is_gem == 1) {
          this.nbGems++;
        }

        if(tile.properties.portal == 1) {
          this.doorSprites.push(tile);
          tile.alpha = 0;
        }
      });
    });
  }

  eraseBlock(x, y, nbTiles = CursorLength) {
    const lengthY = nbTiles;
    const lengthX = nbTiles;
    //check the layers associated to the deletion;
    let objectsRemoves = [];
    let indexRemoval = 0;
    const layerIndex = this.findLayerToDestroy(x, y, lengthX, lengthY);
    //cannot destroy
    if(layerIndex == -1) {
      return;
    }

    for(let xAxis = x; xAxis < x + lengthX; xAxis++) {
      for(let yAxis = y; yAxis < y + lengthY; yAxis++) {
        this.handleCollisionBlockOnErase(xAxis, yAxis, layerIndex);

        const fn = () => {
          const tile = this.map.removeTile(xAxis, yAxis, layerIndex);
          objectsRemoves.push(tile);
        }
        setTimeout(fn, indexRemoval * LengthAnimation);
        indexRemoval++;
        if(indexRemoval > CursorLength) {
          indexRemoval = 0;
        }
      }
    }
    this.removedBlock.push({tiles: objectsRemoves, layerIndex: layerIndex, x, y});
    this.removedBlock.sort(this.sortByLayerIndex);
  }

  removeLayer() {
    for(let x = 0; x < WidthLevel / Size; x++) {
      for(let y = 0; y < HeightLevel / Size; y++) {
        this.eraseBlock(x,y, 1);
      }
    }
  }

  undoLayer() {
    for(let x = 0; x < WidthLevel / Size; x++) {
      for(let y = 0; y < HeightLevel / Size; y++) {
        this.undoBlock(x,y, 1);
      }
    }
  }

  handleCollisionBlockOnErase(x,y, layerIndex) {
    let collidedTile = this.map.getTile(x, y, "colissionLayer");
    if(collidedTile && collidedTile.properties) {
      if(collidedTile.properties.layer_index >= layerIndex) {
        this.cacheCollisionLayer.push(collidedTile);
        this.map.removeTile(x, y, "colissionLayer")
      }
    } else{
      //dont find the tile in the layer, so the tile might be in the deleted tiles
      let indexRemoveCollisionBlock = 0;
      const tileToInsert = this.cacheCollisionLayer.find((tile, index) => {
        indexRemoveCollisionBlock = index;
        return tile.x === x && tile.y === y;
      });
      if(!tileToInsert) {
        return;
      }
      if(tileToInsert.properties.layer_index == layerIndex - 1) {
        let newTile = this.map.putTile(tileToInsert, tileToInsert.x, tileToInsert.y, "colissionLayer");
        //copy property
        newTile.properties = Object.assign({}, tileToInsert.properties);
        if(!this.shouldPicked(newTile)) {
          newTile.alpha = 0;
        }
        this.cacheCollisionLayer.splice(indexRemoveCollisionBlock, 1);
      }
    }
  }

  handleCollisionBlockOnUndo(x,y, layerIndex) {
    let collidedTile = this.map.getTile(x, y, "colissionLayer");
    if(collidedTile && collidedTile.properties) {
      //remove collision block
      if(collidedTile.properties.layer_index < layerIndex) {
        this.cacheCollisionLayer.push(collidedTile);
        this.map.removeTile(x, y, "colissionLayer")
      }
    } else {
      //dont find the tile in the layer, so the tile might be in the deleted tiles
      let indexRemoveCollisionBlock = 0;
      const tileToInsert = this.cacheCollisionLayer.find((tile, index) => {
        indexRemoveCollisionBlock = index;
        return tile.x === x && tile.y === y;
      });
      if(!tileToInsert) {
        return;
      }
      if(tileToInsert.properties.layer_index == layerIndex) {
        let newTile = this.map.putTile(tileToInsert, tileToInsert.x, tileToInsert.y, "colissionLayer");
        //copy property
        newTile.properties = Object.assign({}, tileToInsert.properties);
        if(!this.shouldPicked(newTile)) {
          newTile.alpha = 0;
        }
        this.cacheCollisionLayer.splice(indexRemoveCollisionBlock, 1);
      }
    }
  }

  sortByLayerIndex(a, b) {
    return a.layerIndex > b.layerIndex;
  }

  undoBlock(x, y, nbTiles = CursorLength) {
    const lengthX = nbTiles;
    const lengthY = nbTiles;
    const redoElementsArray = this.removedBlock.filter(list => list.x === x && list.y === y );
    const redoElements = redoElementsArray.reduce((acc, elm) => {
      if(elm.layerIndex < acc.layerIndex) {
        return elm;
      }
      return acc;
    });
    if(redoElements) {
      let indexRemoval = CursorLength;
      redoElements.tiles.forEach(tile => {
        this.handleCollisionBlockOnUndo(tile.x, tile.y, redoElements.layerIndex);
        let collidedTile = this.map.getTile(tile.x, tile.y, "colissionLayer");
        if(collidedTile) {
          collidedTile.isVisible = collidedTile.isVisible ? false : true;
        }
        const fn = () => {
          this.map.putTile(tile, tile.x, tile.y, redoElements.layerIndex);
        };
        setTimeout(fn, indexRemoval * LengthAnimation);
        indexRemoval--;
        if(indexRemoval < 0) {
          indexRemoval = CursorLength;
        }
      });
      //remove the element after
      const newArray = this.removedBlock.filter(elmt => elmt !== redoElements);
      this.removedBlock = newArray.sort(this.sortByLayerIndex);
    }
  }

  killGem() {
    this.currentGems++;
    if(this.nbGems === this.currentGems) {
      this.showDoor();
    }
  }

  portalEnable() {
    return this.visibleDoor;
  }

  showDoor() {
    this.visibleDoor = true;
    this.doorSprites.forEach((tile, index) => {
      tile.alpha = 1;
    });
  }

}

export default MapManager;<commit_msg>display portal<commit_after>import { CursorLength, Width, Height, Size, WidthLevel, HeightLevel } from '../Constants.js';

const LengthAnimation = 50;
const MaxLayer = 3;

class MapManager {

  constructor(map, lastLayerAvailable) {
    this.removedBlock = [];
    this.cacheCollisionLayer = [];
    this.lastLayerAvailable = lastLayerAvailable;
    this.map = map;
    this.nbGems = 0;
    this.currentGems = 0;
    this.doorSprites = [];
    this.visibleDoor = false;
  }

  shouldPicked(tile) {
    return tile.properties.is_gem == 1 || tile.properties.layer_gem == 1;
  }

  findLayerToDestroy(x, y, lengthX, lengthY) {
    //layer can be deleted 3 and 2
    let layerIndex = MaxLayer;
    for(let index = MaxLayer; index > 1; index--) {
      if( this.map.getTile(x, y, index) === null &&
          this.map.getTile(x + lengthX-1, y + lengthY-1, index) === null) {
        layerIndex--;
      } else {
        break;
      }
    }
    //impossibe
    if(layerIndex <= 1 ||  layerIndex <= this.lastLayerAvailable) {
      return -1;
    }
    return layerIndex;
  }

  setUpCollisionLayer(colissionLayer) {
    colissionLayer.layer.data.forEach(column => {
      column.forEach(tile => {
        if(tile.properties.layer_index && tile.properties.layer_index != MaxLayer) {
          this.cacheCollisionLayer.push(tile);
          this.map.removeTile(tile.x, tile.y, "colissionLayer");
         }

         if(tile.properties.layer_index) {
          tile.alpha = 0;
         }

        if(this.shouldPicked(tile)) {
          if(tile.properties.layer_index == MaxLayer) {
            tile.alpha = 1;
          } else {
            tile.alpha = 0;
          }
        }

        if(tile.properties.is_gem == 1) {
          this.nbGems++;
        }

        if(tile.properties.portal == 1) {
          this.doorSprites.push(tile);
          tile.alpha = 0;
        }
      });
    });
  }

  eraseBlock(x, y, nbTiles = CursorLength) {
    const lengthY = nbTiles;
    const lengthX = nbTiles;
    //check the layers associated to the deletion;
    let objectsRemoves = [];
    let indexRemoval = 0;
    const layerIndex = this.findLayerToDestroy(x, y, lengthX, lengthY);
    //cannot destroy
    if(layerIndex == -1) {
      return;
    }
    for(let xAxis = x; xAxis < x + lengthX; xAxis++) {
      for(let yAxis = y; yAxis < y + lengthY; yAxis++) {
        this.handleCollisionBlockOnErase(xAxis, yAxis, layerIndex);

        const fn = () => {
          const tile = this.map.removeTile(xAxis, yAxis, layerIndex);
          objectsRemoves.push(tile);
        }
        setTimeout(fn, indexRemoval * LengthAnimation);
        indexRemoval++;
        if(indexRemoval > CursorLength) {
          indexRemoval = 0;
        }
      }
    }
    this.removedBlock.push({tiles: objectsRemoves, layerIndex: layerIndex, x, y});
    this.removedBlock.sort(this.sortByLayerIndex);
  }

  removeLayer() {
    for(let x = 0; x < WidthLevel / Size; x++) {
      for(let y = 0; y < HeightLevel / Size; y++) {
        this.eraseBlock(x,y, 1);
      }
    }
  }

  undoLayer() {
    for(let x = 0; x < WidthLevel / Size; x++) {
      for(let y = 0; y < HeightLevel / Size; y++) {
        this.undoBlock(x,y, 1);
      }
    }
  }

  handleCollisionBlockOnErase(x,y, layerIndex) {
    let collidedTile = this.map.getTile(x, y, "colissionLayer");
    if(collidedTile && collidedTile.properties) {
      if(collidedTile.properties.layer_index >= layerIndex) {
        if(!collidedTile.properties || !collidedTile.properties.portal) {
          this.cacheCollisionLayer.push(collidedTile);
          this.map.removeTile(x, y, "colissionLayer");
        }
      }
    } else{
      //dont find the tile in the layer, so the tile might be in the deleted tiles
      let indexRemoveCollisionBlock = 0;
      const tileToInsert = this.cacheCollisionLayer.find((tile, index) => {
        indexRemoveCollisionBlock = index;
        return tile.x === x && tile.y === y;
      });
      if(!tileToInsert) {
        return;
      }
      if(tileToInsert.properties.layer_index == layerIndex - 1) {
        let newTile = this.map.putTile(tileToInsert, tileToInsert.x, tileToInsert.y, "colissionLayer");
        //copy property
        newTile.properties = Object.assign({}, tileToInsert.properties);
        if(!this.shouldPicked(newTile)) {
          newTile.alpha = 0;
        }
        this.cacheCollisionLayer.splice(indexRemoveCollisionBlock, 1);
      }
    }
  }

  handleCollisionBlockOnUndo(x,y, layerIndex) {
    let collidedTile = this.map.getTile(x, y, "colissionLayer");
    if(collidedTile && collidedTile.properties) {
      //remove collision block
      if(collidedTile.properties.layer_index < layerIndex) {
        this.cacheCollisionLayer.push(collidedTile);
        this.map.removeTile(x, y, "colissionLayer")
      }
    } else {
      //dont find the tile in the layer, so the tile might be in the deleted tiles
      let indexRemoveCollisionBlock = 0;
      const tileToInsert = this.cacheCollisionLayer.find((tile, index) => {
        indexRemoveCollisionBlock = index;
        return tile.x === x && tile.y === y;
      });
      if(!tileToInsert) {
        return;
      }
      if(tileToInsert.properties.layer_index == layerIndex) {
        let newTile = this.map.putTile(tileToInsert, tileToInsert.x, tileToInsert.y, "colissionLayer");
        //copy property
        newTile.properties = Object.assign({}, tileToInsert.properties);
        if(!this.shouldPicked(newTile)) {
          newTile.alpha = 0;
        }
        this.cacheCollisionLayer.splice(indexRemoveCollisionBlock, 1);
      }
    }
  }

  sortByLayerIndex(a, b) {
    return a.layerIndex > b.layerIndex;
  }

  undoBlock(x, y, nbTiles = CursorLength) {
    const lengthX = nbTiles;
    const lengthY = nbTiles;
    const redoElementsArray = this.removedBlock.filter(list => list.x === x && list.y === y );
    const redoElements = redoElementsArray.reduce((acc, elm) => {
      if(elm.layerIndex < acc.layerIndex) {
        return elm;
      }
      return acc;
    });
    if(redoElements) {
      let indexRemoval = CursorLength;
      redoElements.tiles.forEach(tile => {
        this.handleCollisionBlockOnUndo(tile.x, tile.y, redoElements.layerIndex);
        let collidedTile = this.map.getTile(tile.x, tile.y, "colissionLayer");
        if(collidedTile) {
          collidedTile.isVisible = collidedTile.isVisible ? false : true;
        }
        const fn = () => {
          this.map.putTile(tile, tile.x, tile.y, redoElements.layerIndex);
        };
        setTimeout(fn, indexRemoval * LengthAnimation);
        indexRemoval--;
        if(indexRemoval < 0) {
          indexRemoval = CursorLength;
        }
      });
      //remove the element after
      const newArray = this.removedBlock.filter(elmt => elmt !== redoElements);
      this.removedBlock = newArray.sort(this.sortByLayerIndex);
    }
  }

  killGem() {
    this.currentGems++;
    if(this.nbGems === this.currentGems) {
      this.showDoor();
    }
  }

  portalEnable() {
    return this.visibleDoor;
  }

  showDoor() {
    this.visibleDoor = true;
    this.doorSprites.forEach((tile, index) => {
      tile.alpha = 1;
    });
  }

}

export default MapManager;<|endoftext|>


<commit_before>define([], function () {
    var Util = {};

    // If once is true, after the event has been fired, any further handlers which are
    // registered will fire immediately, and this type of event cannot be fired twice.
    Util.mkEvent = function (once) {
        var handlers = [];
        var fired = false;
        return {
            reg: function (cb) {
                if (once && fired) { return void setTimeout(cb); }
                handlers.push(cb);
            },
            unreg: function (cb) {
                if (handlers.indexOf(cb) === -1) { throw new Error("Not registered"); }
                handlers.splice(handlers.indexOf(cb), 1);
            },
            fire: function () {
                if (fired) { return; }
                fired = true;
                handlers.forEach(function (h) { h(); });
            }
        };
    };

    Util.find = function (map, path) {
        return (map && path.reduce(function (p, n) {
            return typeof(p[n]) !== 'undefined' && p[n];
        }, map));
    };

    Util.uid = function () {
        return Number(Math.floor(Math.random() * Number.MAX_SAFE_INTEGER))
            .toString(32).replace(/\./g, '');
    };

    Util.fixHTML = function (str) {
        if (!str) { return ''; }
        return str.replace(/[<>&"']/g, function (x) {
            return ({ "<": "<", ">": "&gt", "&": "&", '"': """, "'": "'" })[x];
        });
    };

    Util.hexToBase64 = function (hex) {
        var hexArray = hex
            .replace(/\r|\n/g, "")
            .replace(/([\da-fA-F]{2}) ?/g, "0x$1 ")
            .replace(/ +$/, "")
            .split(" ");
        var byteString = String.fromCharCode.apply(null, hexArray);
        return window.btoa(byteString).replace(/\//g, '-').replace(/=+$/, '');
    };

    Util.base64ToHex = function (b64String) {
        var hexArray = [];
        atob(b64String.replace(/-/g, '/')).split("").forEach(function(e){
            var h = e.charCodeAt(0).toString(16);
            if (h.length === 1) { h = "0"+h; }
            hexArray.push(h);
        });
        return hexArray.join("");
    };

    Util.uint8ArrayToHex = function (a) {
        // call slice so Uint8Arrays work as expected
        return Array.prototype.slice.call(a).map(function (e) {
            var n = Number(e & 0xff).toString(16);
            if (n === 'NaN') {
                throw new Error('invalid input resulted in NaN');
            }

            switch (n.length) {
                case 0: return '00'; // just being careful, shouldn't happen
                case 1: return '0' + n;
                case 2: return n;
                default: throw new Error('unexpected value');
            }
        }).join('');
    };

    Util.deduplicateString = function (array) {
        var a = array.slice();
        for(var i=0; i<a.length; i++) {
            for(var j=i+1; j<a.length; j++) {
                if(a[i] === a[j]) { a.splice(j--, 1); }
            }
        }
        return a;
    };

    Util.getHash = function () {
        return window.location.hash.slice(1);
    };

    Util.replaceHash = function (hash) {
        if (window.history && window.history.replaceState) {
            if (!/^#/.test(hash)) { hash = '#' + hash; }
            void window.history.replaceState({}, window.document.title, hash);
            if (typeof(window.onhashchange) === 'function') {
                window.onhashchange();
            }
            return;
        }
        window.location.hash = hash;
    };

    /*
     *  Saving files
     */
    Util.fixFileName = function (filename) {
        return filename.replace(/ /g, '-').replace(/[\/\?]/g, '_')
            .replace(/_+/g, '_');
    };

    var oneKilobyte = 1024;
    var oneMegabyte = 1024 * oneKilobyte;
    var oneGigabyte = 1024 * oneMegabyte;

    Util.bytesToGigabytes = function (bytes) {
        return Math.ceil(bytes / oneGigabyte * 100) / 100;
    };

    Util.bytesToMegabytes = function (bytes) {
        return Math.ceil(bytes / oneMegabyte * 100) / 100;
    };

    Util.bytesToKilobytes = function (bytes) {
        return Math.ceil(bytes / oneKilobyte * 100) / 100;
    };

    Util.magnitudeOfBytes = function (bytes) {
        if (bytes >= oneGigabyte) { return 'GB'; }
        else if (bytes >= oneMegabyte) { return 'MB'; }
    };

    Util.fetch = function (src, cb) {
        var done = false;
        var CB = function (err, res) {
            if (done) { return; }
            done = true;
            cb(err, res);
        };

        var xhr = new XMLHttpRequest();
        xhr.open("GET", src, true);
        xhr.responseType = "arraybuffer";
        xhr.onload = function () {
            if (/^4/.test(''+this.status)) {
                return CB('XHR_ERROR');
            }
            return void CB(void 0, new Uint8Array(xhr.response));
        };
        xhr.send(null);
    };

    Util.throttle = function (f, ms) {
        var to;
        var g = function () {
            window.clearTimeout(to);
            to = window.setTimeout(f, ms);
        };
        return g;
    };

    /*  takes a function (f) and a time (t) in ms. returns a function wrapper
        which prevents the internal function from being called more than once
        every t ms. if the function is prevented, returns time til next valid
        execution, else null.
    */
    Util.notAgainForAnother = function (f, t) {
        if (typeof(f) !== 'function' || typeof(t) !== 'number') {
            throw new Error("invalid inputs");
        }
        var last = null;
        return function () {
            var now = +new Date();
            if (last && now <= last + t) { return t - (now - last); }
            last = now;
            f();
            return null;
        };
    };

    Util.createRandomInteger = function () {
        return Math.floor(Math.random() * Number.MAX_SAFE_INTEGER);
    };

    Util.getAppType = function () {
        var parts = window.location.pathname.split('/')
            .filter(function (x) { return x; });
        if (!parts[0]) { return ''; }
        return parts[0];
    };

    /* for wrapping async functions such that they can only be called once */
    Util.once = function (f) {
        var called;
        return function () {
            if (called) { return; }
            called = true;
            f.apply(this, Array.prototype.slice.call(arguments));
        };
    };

    return Util;
});
<commit_msg>Allow passing arguments when firing events<commit_after>define([], function () {
    var Util = {};

    // If once is true, after the event has been fired, any further handlers which are
    // registered will fire immediately, and this type of event cannot be fired twice.
    Util.mkEvent = function (once) {
        var handlers = [];
        var fired = false;
        return {
            reg: function (cb) {
                if (once && fired) { return void setTimeout(cb); }
                handlers.push(cb);
            },
            unreg: function (cb) {
                if (handlers.indexOf(cb) === -1) { throw new Error("Not registered"); }
                handlers.splice(handlers.indexOf(cb), 1);
            },
            fire: function () {
                if (fired) { return; }
                fired = true;
                var args = Array.prototype.slice.call(arguments);
                handlers.forEach(function (h) { h.apply(null, args); });
            }
        };
    };

    Util.find = function (map, path) {
        return (map && path.reduce(function (p, n) {
            return typeof(p[n]) !== 'undefined' && p[n];
        }, map));
    };

    Util.uid = function () {
        return Number(Math.floor(Math.random() * Number.MAX_SAFE_INTEGER))
            .toString(32).replace(/\./g, '');
    };

    Util.fixHTML = function (str) {
        if (!str) { return ''; }
        return str.replace(/[<>&"']/g, function (x) {
            return ({ "<": "<", ">": "&gt", "&": "&", '"': """, "'": "'" })[x];
        });
    };

    Util.hexToBase64 = function (hex) {
        var hexArray = hex
            .replace(/\r|\n/g, "")
            .replace(/([\da-fA-F]{2}) ?/g, "0x$1 ")
            .replace(/ +$/, "")
            .split(" ");
        var byteString = String.fromCharCode.apply(null, hexArray);
        return window.btoa(byteString).replace(/\//g, '-').replace(/=+$/, '');
    };

    Util.base64ToHex = function (b64String) {
        var hexArray = [];
        atob(b64String.replace(/-/g, '/')).split("").forEach(function(e){
            var h = e.charCodeAt(0).toString(16);
            if (h.length === 1) { h = "0"+h; }
            hexArray.push(h);
        });
        return hexArray.join("");
    };

    Util.uint8ArrayToHex = function (a) {
        // call slice so Uint8Arrays work as expected
        return Array.prototype.slice.call(a).map(function (e) {
            var n = Number(e & 0xff).toString(16);
            if (n === 'NaN') {
                throw new Error('invalid input resulted in NaN');
            }

            switch (n.length) {
                case 0: return '00'; // just being careful, shouldn't happen
                case 1: return '0' + n;
                case 2: return n;
                default: throw new Error('unexpected value');
            }
        }).join('');
    };

    Util.deduplicateString = function (array) {
        var a = array.slice();
        for(var i=0; i<a.length; i++) {
            for(var j=i+1; j<a.length; j++) {
                if(a[i] === a[j]) { a.splice(j--, 1); }
            }
        }
        return a;
    };

    Util.getHash = function () {
        return window.location.hash.slice(1);
    };

    Util.replaceHash = function (hash) {
        if (window.history && window.history.replaceState) {
            if (!/^#/.test(hash)) { hash = '#' + hash; }
            void window.history.replaceState({}, window.document.title, hash);
            if (typeof(window.onhashchange) === 'function') {
                window.onhashchange();
            }
            return;
        }
        window.location.hash = hash;
    };

    /*
     *  Saving files
     */
    Util.fixFileName = function (filename) {
        return filename.replace(/ /g, '-').replace(/[\/\?]/g, '_')
            .replace(/_+/g, '_');
    };

    var oneKilobyte = 1024;
    var oneMegabyte = 1024 * oneKilobyte;
    var oneGigabyte = 1024 * oneMegabyte;

    Util.bytesToGigabytes = function (bytes) {
        return Math.ceil(bytes / oneGigabyte * 100) / 100;
    };

    Util.bytesToMegabytes = function (bytes) {
        return Math.ceil(bytes / oneMegabyte * 100) / 100;
    };

    Util.bytesToKilobytes = function (bytes) {
        return Math.ceil(bytes / oneKilobyte * 100) / 100;
    };

    Util.magnitudeOfBytes = function (bytes) {
        if (bytes >= oneGigabyte) { return 'GB'; }
        else if (bytes >= oneMegabyte) { return 'MB'; }
    };

    Util.fetch = function (src, cb) {
        var done = false;
        var CB = function (err, res) {
            if (done) { return; }
            done = true;
            cb(err, res);
        };

        var xhr = new XMLHttpRequest();
        xhr.open("GET", src, true);
        xhr.responseType = "arraybuffer";
        xhr.onload = function () {
            if (/^4/.test(''+this.status)) {
                return CB('XHR_ERROR');
            }
            return void CB(void 0, new Uint8Array(xhr.response));
        };
        xhr.send(null);
    };

    Util.throttle = function (f, ms) {
        var to;
        var g = function () {
            window.clearTimeout(to);
            to = window.setTimeout(f, ms);
        };
        return g;
    };

    /*  takes a function (f) and a time (t) in ms. returns a function wrapper
        which prevents the internal function from being called more than once
        every t ms. if the function is prevented, returns time til next valid
        execution, else null.
    */
    Util.notAgainForAnother = function (f, t) {
        if (typeof(f) !== 'function' || typeof(t) !== 'number') {
            throw new Error("invalid inputs");
        }
        var last = null;
        return function () {
            var now = +new Date();
            if (last && now <= last + t) { return t - (now - last); }
            last = now;
            f();
            return null;
        };
    };

    Util.createRandomInteger = function () {
        return Math.floor(Math.random() * Number.MAX_SAFE_INTEGER);
    };

    Util.getAppType = function () {
        var parts = window.location.pathname.split('/')
            .filter(function (x) { return x; });
        if (!parts[0]) { return ''; }
        return parts[0];
    };

    /* for wrapping async functions such that they can only be called once */
    Util.once = function (f) {
        var called;
        return function () {
            if (called) { return; }
            called = true;
            f.apply(this, Array.prototype.slice.call(arguments));
        };
    };

    return Util;
});
<|endoftext|>


<commit_before>/**
 * caseControllers is a container for all case related Controllers
 */
angular.module('caseControllers', [
    // Angular dependencies
    'ngCookies',

    // 3rd party
    'ui.bootstrap',

    // Lily dependencies
    'caseServices'
])

    /**
     * CaseListController controller to show list of cases
     *
     */
    .controller('CaseListController', [
        '$scope',
        '$cookieStore',
        '$window',

        'Case',
        'Cookie',
        function($scope, $cookieStore, $window, Case, Cookie) {

            Cookie.prefix ='caseList';

            /**
             * table object: stores all the information to correctly display the table
             */
            $scope.table = {
                page: 1,  // current page of pagination: 1-index
                pageSize: 20,  // number of items per page
                totalItems: 0, // total number of items
                filter: Cookie.getCookieValue('filter', ''),  // search filter
                archived: Cookie.getCookieValue('archived', false),
                order:  Cookie.getCookieValue('order', {
                    ascending: true,
                    column:  'expires'  // string: current sorted column
                }),
                visibility: Cookie.getCookieValue('visibility', {
                    caseId: true,
                    client: true,
                    subject: true,
                    priority: true,
                    type: true,
                    status: true,
                    expires: true,
                    assignedTo: true,
                    tags: true
                })};

            /**
             * updateTableSettings() sets scope variables to the cookie
             */
            function updateTableSettings() {
                Cookie.setCookieValue('filter', $scope.table.filter);
                Cookie.setCookieValue('archived', $scope.table.archived);
                Cookie.setCookieValue('order', $scope.table.order);
                Cookie.setCookieValue('visibility', $scope.table.visibility);
            }

            /**
             * updateCases() reloads the cases trough a service
             *
             * Updates table.items and table.totalItems
             */
            function updateCases() {
                Case.query(
                    $scope.table
                ).then(function(data) {
                        $scope.table.items = data.cases;
                        $scope.table.totalItems = data.total;
                    }
                );
            }

            /**
             * Watches the model info from the table that, when changed,
             * needs a new set of cases
             */
            $scope.$watchGroup([
                'table.page',
                'table.order.column',
                'table.order.ascending',
                'table.filter',
                'table.archived'
            ], function() {
                updateTableSettings();
                updateCases();
            });

            /**
             * Watches the model info from the table that, when changed,
             * needs to store the info to the cache
             */
            $scope.$watchCollection('table.visibility', function() {
               updateTableSettings();
            });

            /**
             * setFilter() sets the filter of the table
             *
             * @param queryString string: string that will be set as the new filter on the table
             */
            $scope.setFilter = function(queryString) {
                $scope.table.filter = queryString;
            };

            $scope.toggleArchived = function() {
                $scope.table.archived = !$scope.table.archived;
            }
        }
    ]
);
<commit_msg>Hotfix: case list 60 items per page<commit_after>/**
 * caseControllers is a container for all case related Controllers
 */
angular.module('caseControllers', [
    // Angular dependencies
    'ngCookies',

    // 3rd party
    'ui.bootstrap',

    // Lily dependencies
    'caseServices'
])

    /**
     * CaseListController controller to show list of cases
     *
     */
    .controller('CaseListController', [
        '$scope',
        '$cookieStore',
        '$window',

        'Case',
        'Cookie',
        function($scope, $cookieStore, $window, Case, Cookie) {

            Cookie.prefix ='caseList';

            /**
             * table object: stores all the information to correctly display the table
             */
            $scope.table = {
                page: 1,  // current page of pagination: 1-index
                pageSize: 60,  // number of items per page
                totalItems: 0, // total number of items
                filter: Cookie.getCookieValue('filter', ''),  // search filter
                archived: Cookie.getCookieValue('archived', false),
                order:  Cookie.getCookieValue('order', {
                    ascending: true,
                    column:  'expires'  // string: current sorted column
                }),
                visibility: Cookie.getCookieValue('visibility', {
                    caseId: true,
                    client: true,
                    subject: true,
                    priority: true,
                    type: true,
                    status: true,
                    expires: true,
                    assignedTo: true,
                    tags: true
                })};

            /**
             * updateTableSettings() sets scope variables to the cookie
             */
            function updateTableSettings() {
                Cookie.setCookieValue('filter', $scope.table.filter);
                Cookie.setCookieValue('archived', $scope.table.archived);
                Cookie.setCookieValue('order', $scope.table.order);
                Cookie.setCookieValue('visibility', $scope.table.visibility);
            }

            /**
             * updateCases() reloads the cases trough a service
             *
             * Updates table.items and table.totalItems
             */
            function updateCases() {
                Case.query(
                    $scope.table
                ).then(function(data) {
                        $scope.table.items = data.cases;
                        $scope.table.totalItems = data.total;
                    }
                );
            }

            /**
             * Watches the model info from the table that, when changed,
             * needs a new set of cases
             */
            $scope.$watchGroup([
                'table.page',
                'table.order.column',
                'table.order.ascending',
                'table.filter',
                'table.archived'
            ], function() {
                updateTableSettings();
                updateCases();
            });

            /**
             * Watches the model info from the table that, when changed,
             * needs to store the info to the cache
             */
            $scope.$watchCollection('table.visibility', function() {
               updateTableSettings();
            });

            /**
             * setFilter() sets the filter of the table
             *
             * @param queryString string: string that will be set as the new filter on the table
             */
            $scope.setFilter = function(queryString) {
                $scope.table.filter = queryString;
            };

            $scope.toggleArchived = function() {
                $scope.table.archived = !$scope.table.archived;
            }
        }
    ]
);
<|endoftext|>


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

"use strict";

var iotdb = require('iotdb');
var _ = iotdb._;
var bunyan = iotdb.bunyan;

var littlebits = require('littlebits-cloud-http');

var logger = bunyan.createLogger({
    name: 'homestar-littlebits',
    module: 'LittleBitsBridge',
});

/**
 *  See {iotdb.bridge.Bridge#Bridge} for documentation.
 *

 *  @param {object|undefined} native
 *  only used for instances, should be
 */
var LittleBitsBridge = function (initd, native) {
    var self = this;

    self.initd = _.defaults(initd, {
        access_token: null,
        id: null,
        name: null,
    });
    self.native = native;

    if (self.native) {
        self.connected = {};
    }
};

LittleBitsBridge.prototype = new iotdb.Bridge();

LittleBitsBridge.prototype.name = function () {
    return "LittleBitsBridge";
};

/* --- lifecycle --- */

/**
 *  See {iotdb.bridge.Bridge#XXX} for documentation.
 */
LittleBitsBridge.prototype.discover = function () {
    var self = this;

    var api = self._api();
    if (!api) {
        logger.error({
            method: "discover",
        }, "LittleBits is not configured correctly");
        return;
    }

    api.devices(function(error, devices) {
        for (var di in devices) {
            var device = devices[di];
            if (self.initd.id && (device.id !== self.initd.id)) {
                continue;
            } else if (self.initd.name && (device.label !== self.initd.name)) {
                continue;
            }

            var native = api.defaults({
                device_id: device.id,
            });

            var initd = _.defaults({
                id: device.id,
                name: device.label,
            }, self.initd);

            self.discovered(new LittleBitsBridge(initd, native));
        };
    });
};

/**
 *  See {iotdb.bridge.Bridge#XXX} for documentation.
 */
LittleBitsBridge.prototype.connect = function (connectd) {
    var self = this;
    if (!self.native) {
        return;
    }

    self.connectd = _.defaults(connectd, {
        subscribes: [],
        data_in: function (paramd) {
            paramd.cookd = paramd.rawd;
        },
        data_out: function (paramd) {
            paramd.rawd = paramd.cookd;
        },
    });

    self._setup_events();
};

LittleBitsBridge.prototype._setup_events = function () {
    var self = this;

    for (var si in self.connectd.subscribes) {
        self._setup_event(self.connectd.subscribes[si]);
    }
};

LittleBitsBridge.prototype._setup_event = function (subscribe_id) {
    var self = this;

    self.native.subscribe({
        subscriber_id: self.initd.id,
        publisher_id: self.initd.id,
        publisher_events: [
            "amplitude",
            "amplitude:delta:sustain",
            "amplitude:delta:ignite",
            "amplitude:delta:release",
            "amplitude:delta:nap",
            "amplitude:level:active",
            "amplitude:level:idle",
        ]
    }, function() {
        // console.log("HERE:EVENT.ACTIVE", arguments);
    });

};

LittleBitsBridge.prototype._forget = function () {
    var self = this;
    if (!self.native) {
        return;
    }

    logger.info({
        method: "_forget"
    }, "called");

    self.native = null;
    self.pulled();
};

/**
 *  See {iotdb.bridge.Bridge#XXX} for documentation.
 */
LittleBitsBridge.prototype.disconnect = function () {
    var self = this;
    if (!self.native || !self.native) {
        return;
    }

    self._forget();
};

/* --- data --- */

/**
 *  See {iotdb.bridge.Bridge#XXX} for documentation.
 */
LittleBitsBridge.prototype.push = function (pushd) {
    var self = this;
    if (!self.native) {
        return;
    }

    var paramd = {
        cookd: pushd,
        rawd: {},
    };
    self.connectd.data_out(paramd);

    logger.info({
        method: "push",
        unique_id: self.unique_id,
        cookd: paramd.cookd,
        rawd: paramd.rawd,
    }, "pushed");

    if (paramd.rawd.percent !== undefined) {
        self.native.output({
            percent: paramd.rawd.percent,
            duration_ms: -1,
        }, function() {
            // console.log("HERE:RESULT", arguments);
        });
    } else if (paramd.rawd.color) {
        /*

        self.native.light({
            color: "yellow",
            mode: "blink",
        }, function() {
        });
        */
    }

    /*
    if (pushed.mode) {
    }

    if (pushed.duration) {
    }

    if (pushed.percent) {
    }

    percent :: Float | >= 0, <= 100

    color :: String | "green" "yellow" "red" "blue" "purple" "white" "cyan" "clownbarf"
    mode :: String | "blink" "hold"
    duration_ms :: Integer | >= 0
    device_id :: String
    */
};

/**
 *  See {iotdb.bridge.Bridge#XXX} for documentation.
 */
LittleBitsBridge.prototype.pull = function () {
    var self = this;
    if (!self.native) {
        return;
    }
};

/* --- state --- */

/**
 *  See {iotdb.bridge.Bridge#XXX} for documentation.
 */
LittleBitsBridge.prototype.meta = function () {
    var self = this;
    if (!self.native) {
        return;
    }

    return {
        "iot:thing": _.id.thing_urn.unique("LittleBits", self.initd.id),
        "schema:name": self.initd.name || "LittleBits",
        "schema:manufacturer": "http://littlebits.cc/",
        "schema:model": "http://littlebits.cc/cloud",
    };
};

/**
 *  See {iotdb.bridge.Bridge#XXX} for documentation.
 */
LittleBitsBridge.prototype.reachable = function () {
    if (this.native === null) {
        return false;
    }

    return this.native.is_connected;
};

/**
 *  See {iotdb.bridge.Bridge#XXX} for documentation.
 */
LittleBitsBridge.prototype.configure = function (app) {};

/* --- internals -- */
var __api;

LittleBitsBridge.prototype._api = function () {
    var self = this;

    /* special case */
    if (self.initd.access_token) {
        return littlebits.defaults({
            access_token: self.initd.access_token,
        });
    }

    /* normal case */
    if (!__api) {
        var access_token = iotdb.keystore().get("/bridges/LittleBitsBridge/account/access_token");
        if (!access_token) {
            logger.error({
                method: "_api",
                cause: "set /bridges/LittleBitsBridge/account/access_token",
            }, "LittleBits is not configured correctly");
            return;
        }

        __api = littlebits.defaults({
            access_token: access_token,
        });
    }

    return __api;
}

/*
 *  API
 */
exports.Bridge = LittleBitsBridge;
<commit_msg>deal with 'connectd'<commit_after>/*
 *  LittleBitsBridge.js
 *
 *  David Janes
 *  IOTDB.org
 *  2015-03-22
 *
 *  Copyright [2013-2015] [David P. Janes]
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

"use strict";

var iotdb = require('iotdb');
var _ = iotdb._;
var bunyan = iotdb.bunyan;

var littlebits = require('littlebits-cloud-http');

var logger = bunyan.createLogger({
    name: 'homestar-littlebits',
    module: 'LittleBitsBridge',
});

/**
 *  See {iotdb.bridge.Bridge#Bridge} for documentation.
 *

 *  @param {object|undefined} native
 *  only used for instances, should be
 */
var LittleBitsBridge = function (initd, native) {
    var self = this;

    self.initd = _.defaults(initd, {
        access_token: null,
        id: null,
        name: null,
    });
    self.native = native;

    if (self.native) {
        self.connected = {};
    }
};

LittleBitsBridge.prototype = new iotdb.Bridge();

LittleBitsBridge.prototype.name = function () {
    return "LittleBitsBridge";
};

/* --- lifecycle --- */

/**
 *  See {iotdb.bridge.Bridge#XXX} for documentation.
 */
LittleBitsBridge.prototype.discover = function () {
    var self = this;

    var api = self._api();
    if (!api) {
        logger.error({
            method: "discover",
        }, "LittleBits is not configured correctly");
        return;
    }

    api.devices(function(error, devices) {
        for (var di in devices) {
            var device = devices[di];
            if (self.initd.id && (device.id !== self.initd.id)) {
                continue;
            } else if (self.initd.name && (device.label !== self.initd.name)) {
                continue;
            }

            var native = api.defaults({
                device_id: device.id,
            });

            var initd = _.defaults({
                id: device.id,
                name: device.label,
            }, self.initd);

            self.discovered(new LittleBitsBridge(initd, native));
        };
    });
};

/**
 *  See {iotdb.bridge.Bridge#XXX} for documentation.
 */
LittleBitsBridge.prototype.connect = function (connectd) {
    var self = this;
    if (!self.native) {
        return;
    }

    self._validate_connect(connectd);

    self.connectd = _.defaults(
        connectd,
        {
            subscribes: [],
        },
        self.connectd
    );

    self._setup_events();
};

LittleBitsBridge.prototype._setup_events = function () {
    var self = this;

    for (var si in self.connectd.subscribes) {
        self._setup_event(self.connectd.subscribes[si]);
    }
};

LittleBitsBridge.prototype._setup_event = function (subscribe_id) {
    var self = this;

    self.native.subscribe({
        subscriber_id: self.initd.id,
        publisher_id: self.initd.id,
        publisher_events: [
            "amplitude",
            "amplitude:delta:sustain",
            "amplitude:delta:ignite",
            "amplitude:delta:release",
            "amplitude:delta:nap",
            "amplitude:level:active",
            "amplitude:level:idle",
        ]
    }, function() {
        // console.log("HERE:EVENT.ACTIVE", arguments);
    });

};

LittleBitsBridge.prototype._forget = function () {
    var self = this;
    if (!self.native) {
        return;
    }

    logger.info({
        method: "_forget"
    }, "called");

    self.native = null;
    self.pulled();
};

/**
 *  See {iotdb.bridge.Bridge#XXX} for documentation.
 */
LittleBitsBridge.prototype.disconnect = function () {
    var self = this;
    if (!self.native || !self.native) {
        return;
    }

    self._forget();
};

/* --- data --- */

/**
 *  See {iotdb.bridge.Bridge#XXX} for documentation.
 */
LittleBitsBridge.prototype.push = function (pushd) {
    var self = this;
    if (!self.native) {
        return;
    }

    var paramd = {
        cookd: pushd,
        rawd: {},
    };
    self.connectd.data_out(paramd);

    logger.info({
        method: "push",
        unique_id: self.unique_id,
        cookd: paramd.cookd,
        rawd: paramd.rawd,
    }, "pushed");

    if (paramd.rawd.percent !== undefined) {
        self.native.output({
            percent: paramd.rawd.percent,
            duration_ms: -1,
        }, function() {
            // console.log("HERE:RESULT", arguments);
        });
    } else if (paramd.rawd.color) {
        /*

        self.native.light({
            color: "yellow",
            mode: "blink",
        }, function() {
        });
        */
    }

    /*
    if (pushed.mode) {
    }

    if (pushed.duration) {
    }

    if (pushed.percent) {
    }

    percent :: Float | >= 0, <= 100

    color :: String | "green" "yellow" "red" "blue" "purple" "white" "cyan" "clownbarf"
    mode :: String | "blink" "hold"
    duration_ms :: Integer | >= 0
    device_id :: String
    */
};

/**
 *  See {iotdb.bridge.Bridge#XXX} for documentation.
 */
LittleBitsBridge.prototype.pull = function () {
    var self = this;
    if (!self.native) {
        return;
    }
};

/* --- state --- */

/**
 *  See {iotdb.bridge.Bridge#XXX} for documentation.
 */
LittleBitsBridge.prototype.meta = function () {
    var self = this;
    if (!self.native) {
        return;
    }

    return {
        "iot:thing": _.id.thing_urn.unique("LittleBits", self.initd.id),
        "schema:name": self.initd.name || "LittleBits",
        "schema:manufacturer": "http://littlebits.cc/",
        "schema:model": "http://littlebits.cc/cloud",
    };
};

/**
 *  See {iotdb.bridge.Bridge#XXX} for documentation.
 */
LittleBitsBridge.prototype.reachable = function () {
    if (this.native === null) {
        return false;
    }

    return this.native.is_connected;
};

/**
 *  See {iotdb.bridge.Bridge#XXX} for documentation.
 */
LittleBitsBridge.prototype.configure = function (app) {};

/* --- internals -- */
var __api;

LittleBitsBridge.prototype._api = function () {
    var self = this;

    /* special case */
    if (self.initd.access_token) {
        return littlebits.defaults({
            access_token: self.initd.access_token,
        });
    }

    /* normal case */
    if (!__api) {
        var access_token = iotdb.keystore().get("/bridges/LittleBitsBridge/account/access_token");
        if (!access_token) {
            logger.error({
                method: "_api",
                cause: "set /bridges/LittleBitsBridge/account/access_token",
            }, "LittleBits is not configured correctly");
            return;
        }

        __api = littlebits.defaults({
            access_token: access_token,
        });
    }

    return __api;
}

/*
 *  API
 */
exports.Bridge = LittleBitsBridge;
<|endoftext|>


<commit_before>import * as d3 from 'd3';
import moment from 'moment';

import './styles.scss';

// ------------------------------------------------------------------------------
// Constants required for rendering the graph
// ------------------------------------------------------------------------------
const eventMarkerWidthInPx = 5; // The event marker width
const eventMarkerHeightInPx = 10; // The event marker height
const paddingBetweenStackedEventsInPx = 2; // how much spacing in the y direction to put between stacked events
const cardHeightInPx = 162;

// Given a number of events shaped like `{countChange, timestamp}`, return a number of markers to
// draw on the graph. Markers have a position and elevation - the former being an X pixel position that
// the marker should be drawn at, and the later being the height (in markers) the marker should be
// offset from the center line.
function calculateMarkerPositions(events, timeScale, nowInMs=moment().utc().valueOf()) {
  let lastIngressPosition = null;
  let lastIngressElevation = null;
  let lastEgressPosition = null;
  let lastEgressElevation = null;

  // Loop through events and return markers
  return events.map(event => { // Loop through each marker, and accumulate into `markersToDraw`.
    let position = timeScale(moment(event.timestamp).valueOf()) - eventMarkerWidthInPx;
    let elevation = event.countChange;
    if (event.countChange === 1) {
      if (position && position < lastIngressPosition + eventMarkerWidthInPx + paddingBetweenStackedEventsInPx) {
        position = lastIngressPosition;
        elevation = lastIngressElevation + 1;
      }
      lastIngressPosition = position;
      lastIngressElevation = elevation;
      return { position, elevation };
    } else if (event.countChange === -1) {
      if (position && position < lastEgressPosition + eventMarkerWidthInPx + paddingBetweenStackedEventsInPx) {
        position = lastEgressPosition;
        elevation = lastEgressElevation - 1;
      }
      lastEgressPosition = position;
      lastEgressElevation = elevation;
      return { position, elevation };
    }
  });
}

export default function ingressEgress(elem) {
  const card = d3.select(elem).append('div')
    .attr('class', 'card card-dark ingress-egress-card')

  // add legend to card to explain what stuff means
  const legend = card.append('span')
    .attr('class', 'ingress-egress-card-legend')

  legend.append('div')
    .attr('class', 'rect rect-in')
    .attr('style', `width: ${eventMarkerWidthInPx}px; height: ${eventMarkerHeightInPx}px`)
  legend.append('span').text('In')

  legend.append('div')
    .attr('class', 'rect rect-out')
    .attr('style', `width: ${eventMarkerWidthInPx}px; height: ${eventMarkerHeightInPx}px`)
  legend.append('span').text('Out')

  // Svg to put all the data in
  const svg = card.append('svg')
    .attr('class', 'ingress-egress-data')
    .attr('width', '100%')
    .attr('height', '100%')

  // Render the line down the middle
  svg.append('line')
    .attr('class', 'mid-line')
    .attr('x1', '0')
    .attr('y1', '50%')
    .attr('x2', '100%')
    .attr('y2', '50%')

  const dataGroup = svg.append('g')
    .attr('class', 'ingress-egress-data')

  // Labels at the bottom of the card
  const labels = card.append('ul')
    .attr('class', 'ingress-egress-card-labels')

  return props => {
    // Get the graph's length in minutes for drawing the scale below.
    const graphDurationInMin = props.graphDurationInMin || 10;

    // Get the graph's width from the bounding box of the svg.
    const graphWidth = svg.node().getBBox().width;

    // Draw labels on the bottom of the graph
    const labelSelection = labels.selectAll('li').data([
      `${graphDurationInMin}m ago`,
      `${graphDurationInMin * 0.75}m ago`,
      `${graphDurationInMin * 0.50}m ago`,
      `${graphDurationInMin * 0.25}m ago`,
      `Now`,
    ]);
    const labelEnterSelection = labelSelection.enter().append('li');
    labelSelection.merge(labelEnterSelection).text(d => d);
    labelSelection.exit().remove();

    // Construct a scale for drawing the time series
    const now = moment.utc();
    const timeScale = d3.scaleLinear()
      .rangeRound([graphWidth, 0])
      .domain([
        now.valueOf(), // Get the time in unix time (milliseconds)
        now.subtract(graphDurationInMin, 'minutes').valueOf(),
      ]);

    // Render data in the chart.
    const dataSelection = dataGroup.selectAll('rect').data(
      calculateMarkerPositions(props.events, timeScale, now.valueOf())
        .filter(i => i.position && i.position <= graphWidth) // Only draw points that fit into the graph.
    );

    const dataEnterSelection = dataSelection.enter().append('rect')
      .attr('class', 'count-marker')
      .attr('width', eventMarkerWidthInPx)
      .attr('height', eventMarkerHeightInPx)
    dataSelection.merge(dataEnterSelection)
      .attr('x', d => d.position)
      .attr('y', d => {
        if (d.elevation > 0) {
          return (cardHeightInPx / 2) - // Center the marker
          (d.elevation * (eventMarkerHeightInPx + paddingBetweenStackedEventsInPx));
        } else {
          return (cardHeightInPx / 2) - // Center the marker
          eventMarkerHeightInPx - // position by the top of marker, not bottom
          (d.elevation * (eventMarkerHeightInPx + paddingBetweenStackedEventsInPx)); //
        }
      })
      .attr('class', d => `count-marker ${d.elevation > 0 ? 'count-marker-in' : 'count-marker-out'}`);

    dataSelection.exit().remove()
  };
}
<commit_msg>code: filter out unmatched time series items (resets)<commit_after>import * as d3 from 'd3';
import moment from 'moment';

import './styles.scss';

// ------------------------------------------------------------------------------
// Constants required for rendering the graph
// ------------------------------------------------------------------------------
const eventMarkerWidthInPx = 5; // The event marker width
const eventMarkerHeightInPx = 10; // The event marker height
const paddingBetweenStackedEventsInPx = 2; // how much spacing in the y direction to put between stacked events
const cardHeightInPx = 162;

// Given a number of events shaped like `{countChange, timestamp}`, return a number of markers to
// draw on the graph. Markers have a position and elevation - the former being an X pixel position that
// the marker should be drawn at, and the later being the height (in markers) the marker should be
// offset from the center line.
function calculateMarkerPositions(events, timeScale, nowInMs=moment().utc().valueOf()) {
  let lastIngressPosition = null;
  let lastIngressElevation = null;
  let lastEgressPosition = null;
  let lastEgressElevation = null;

  // Loop through events and return markers
  return events.map(event => { // Loop through each marker, and accumulate into `markersToDraw`.
    let position = timeScale(moment(event.timestamp).valueOf()) - eventMarkerWidthInPx;
    let elevation = event.countChange;
    if (event.countChange === 1) {
      if (position && position < lastIngressPosition + eventMarkerWidthInPx + paddingBetweenStackedEventsInPx) {
        position = lastIngressPosition;
        elevation = lastIngressElevation + 1;
      }
      lastIngressPosition = position;
      lastIngressElevation = elevation;
      return { position, elevation };
    } else if (event.countChange === -1) {
      if (position && position < lastEgressPosition + eventMarkerWidthInPx + paddingBetweenStackedEventsInPx) {
        position = lastEgressPosition;
        elevation = lastEgressElevation - 1;
      }
      lastEgressPosition = position;
      lastEgressElevation = elevation;
      return { position, elevation };
    }
  }).filter(x => x);
}

export default function ingressEgress(elem) {
  const card = d3.select(elem).append('div')
    .attr('class', 'card card-dark ingress-egress-card')

  // add legend to card to explain what stuff means
  const legend = card.append('span')
    .attr('class', 'ingress-egress-card-legend')

  legend.append('div')
    .attr('class', 'rect rect-in')
    .attr('style', `width: ${eventMarkerWidthInPx}px; height: ${eventMarkerHeightInPx}px`)
  legend.append('span').text('In')

  legend.append('div')
    .attr('class', 'rect rect-out')
    .attr('style', `width: ${eventMarkerWidthInPx}px; height: ${eventMarkerHeightInPx}px`)
  legend.append('span').text('Out')

  // Svg to put all the data in
  const svg = card.append('svg')
    .attr('class', 'ingress-egress-data')
    .attr('width', '100%')
    .attr('height', '100%')

  // Render the line down the middle
  svg.append('line')
    .attr('class', 'mid-line')
    .attr('x1', '0')
    .attr('y1', '50%')
    .attr('x2', '100%')
    .attr('y2', '50%')

  const dataGroup = svg.append('g')
    .attr('class', 'ingress-egress-data')

  // Labels at the bottom of the card
  const labels = card.append('ul')
    .attr('class', 'ingress-egress-card-labels')

  return props => {
    // Get the graph's length in minutes for drawing the scale below.
    const graphDurationInMin = props.graphDurationInMin || 10;

    // Get the graph's width from the bounding box of the svg.
    const graphWidth = svg.node().getBBox().width;

    // Draw labels on the bottom of the graph
    const labelSelection = labels.selectAll('li').data([
      `${graphDurationInMin}m ago`,
      `${graphDurationInMin * 0.75}m ago`,
      `${graphDurationInMin * 0.50}m ago`,
      `${graphDurationInMin * 0.25}m ago`,
      `Now`,
    ]);
    const labelEnterSelection = labelSelection.enter().append('li');
    labelSelection.merge(labelEnterSelection).text(d => d);
    labelSelection.exit().remove();

    // Construct a scale for drawing the time series
    const now = moment.utc();
    const timeScale = d3.scaleLinear()
      .rangeRound([graphWidth, 0])
      .domain([
        now.valueOf(), // Get the time in unix time (milliseconds)
        now.subtract(graphDurationInMin, 'minutes').valueOf(),
      ]);

    // Render data in the chart.
    const dataSelection = dataGroup.selectAll('rect').data(
      calculateMarkerPositions(props.events, timeScale, now.valueOf())
        .filter(i => i.position && i.position <= graphWidth) // Only draw points that fit into the graph.
    );

    const dataEnterSelection = dataSelection.enter().append('rect')
      .attr('class', 'count-marker')
      .attr('width', eventMarkerWidthInPx)
      .attr('height', eventMarkerHeightInPx)
    dataSelection.merge(dataEnterSelection)
      .attr('x', d => d.position)
      .attr('y', d => {
        if (d.elevation > 0) {
          return (cardHeightInPx / 2) - // Center the marker
          (d.elevation * (eventMarkerHeightInPx + paddingBetweenStackedEventsInPx));
        } else {
          return (cardHeightInPx / 2) - // Center the marker
          eventMarkerHeightInPx - // position by the top of marker, not bottom
          (d.elevation * (eventMarkerHeightInPx + paddingBetweenStackedEventsInPx)); //
        }
      })
      .attr('class', d => `count-marker ${d.elevation > 0 ? 'count-marker-in' : 'count-marker-out'}`);

    dataSelection.exit().remove()
  };
}
<|endoftext|>


<commit_before>/**
 * @fileoverview Classes to produce farts satisfying various user-defined properties.
 * @version 0.0.14
 * @license GNU Lesser General Public License, version 2.1
 * @author toiletjs (https://twitter.com/toiletjs)
 *
 *  TODO(toiletjs): Use React. See #4 (v2).
 */

class AbstractBaseFart {
  /**
   * Initializes farts.
   * @param {boolean=} opt_isWet Whether the fart is wet. (Optional)
   * @param {boolean=} opt_isSilentButDeadly Whether the fart is silent but deadly. (Optional)
   */
  constructor(opt_isWet, opt_isSilentButDeadly) {
    this.isWet = Boolean(opt_isWet);
    this.isSilentButDeadly = Boolean(opt_isSilentButDeadly);
  }

  /**
   * Farts.
   */
  fart() {}
}

class Fart extends AbstractBaseFart {
  /**
   * Farts.
   */
  fart() {
    if (this.isSilentButDeadly) {
      while(true);
    }
    if (this.isWet) {
      console.log('pfffffpfpfpffffff?');
    } else {
      console.log('poooOOOot.');
    }
  }
}

class Shart extends AbstractBaseFart {
  /**
   * Sharts.
   */
  fart() {
    // Note that this.isWet is not checked here as all sharts are considered wet.
    console.log('plfffffpfffpllffff');
  }
}
<commit_msg>whitespace fix<commit_after>/**
 * @fileoverview Classes to produce farts satisfying various user-defined properties.
 * @version 0.0.14
 * @license GNU Lesser General Public License, version 2.1
 * @author toiletjs (https://twitter.com/toiletjs)
 *
 * TODO(toiletjs): Use React. See #4 (v2).
 */

class AbstractBaseFart {
  /**
   * Initializes farts.
   * @param {boolean=} opt_isWet Whether the fart is wet. (Optional)
   * @param {boolean=} opt_isSilentButDeadly Whether the fart is silent but deadly. (Optional)
   */
  constructor(opt_isWet, opt_isSilentButDeadly) {
    this.isWet = Boolean(opt_isWet);
    this.isSilentButDeadly = Boolean(opt_isSilentButDeadly);
  }

  /**
   * Farts.
   */
  fart() {}
}

class Fart extends AbstractBaseFart {
  /**
   * Farts.
   */
  fart() {
    if (this.isSilentButDeadly) {
      while(true);
    }
    if (this.isWet) {
      console.log('pfffffpfpfpffffff?');
    } else {
      console.log('poooOOOot.');
    }
  }
}

class Shart extends AbstractBaseFart {
  /**
   * Sharts.
   */
  fart() {
    // Note that this.isWet is not checked here as all sharts are considered wet.
    console.log('plfffffpfffpllffff');
  }
}
<|endoftext|>


<commit_before>//	OverAppBrowser.js
//	OverAppBrowser Cordova Plugin
//
//
//  	Copyright 2014 Emmanuel Tabard. All rights reserved.
//      MIT Licensed
//

var exec = require('cordova/exec');
var channel = require('cordova/channel');
var modulemapper = require('cordova/modulemapper');
var urlutil = require('cordova/urlutil');

function OverAppBrowser() {
   this.channels = {
        'loadstart': channel.create('loadstart'),
        'loadstop' : channel.create('loadstop'),
        'loaderror' : channel.create('loaderror'),
        'exit' : channel.create('exit')
   };
}

OverAppBrowser.prototype = {
    _eventHandler: function (event) {
        if (event.type in this.channels) {
            this.channels[event.type].fire(event);
        }
    },
    close: function (eventname) {
        exec(null, null, "OverAppBrowser", "close", []);
    },
    show: function (eventname) {
        exec(null, null, "OverAppBrowser", "show", []);
    },
    fade: function (toAlpha, duration) {
        exec(null, null, "OverAppBrowser", "fade", [toAlpha, duration]);
    },
    resize: function (originx, originy, width, height) {
        exec(null, null, "OverAppBrowser", "resize", [originx, originy, width, height]);
    },
    addEventListener: function (eventname,f) {
        if (eventname in this.channels) {
            this.channels[eventname].subscribe(f);
        }
    },
    removeEventListener: function(eventname, f) {
        if (eventname in this.channels) {
            this.channels[eventname].unsubscribe(f);
        }
    },

    executeScript: function(injectDetails, cb) {
        if (injectDetails.code) {
            exec(cb, null, "OverAppBrowser", "injectScriptCode", [injectDetails.code, !!cb]);
        } else if (injectDetails.file) {
            exec(cb, null, "OverAppBrowser", "injectScriptFile", [injectDetails.file, !!cb]);
        } else {
            throw new Error('executeScript requires exactly one of code or file to be specified');
        }
    },

    insertCSS: function(injectDetails, cb) {
        if (injectDetails.code) {
            exec(cb, null, "OverAppBrowser", "injectStyleCode", [injectDetails.code, !!cb]);
        } else if (injectDetails.file) {
            exec(cb, null, "OverAppBrowser", "injectStyleFile", [injectDetails.file, !!cb]);
        } else {
            throw new Error('insertCSS requires exactly one of code or file to be specified');
        }
    }
};

module.exports = function(strUrl, originx,originy,width,height, isAutoFadeIn) {
    isAutoFadeIn = isAutoFadeIn || true;
    strUrl = urlutil.makeAbsolute(strUrl);
    var oab = new OverAppBrowser();
    var cb = function(eventname) {
       oab._eventHandler(eventname);
    };

    exec(cb, cb, "OverAppBrowser", "open", [strUrl, originx, originy, width, height, isAutoFadeIn]);
    return oab;
};
<commit_msg>WIP android real webview<commit_after>//	OverAppBrowser.js
//	OverAppBrowser Cordova Plugin
//
//
//  	Copyright 2014 Emmanuel Tabard. All rights reserved.
//      MIT Licensed
//

var exec = require('cordova/exec');
var channel = require('cordova/channel');
var modulemapper = require('cordova/modulemapper');
var urlutil = require('cordova/urlutil');

function OverAppBrowser() {
   this.channels = {
        'loadstart': channel.create('loadstart'),
        'loadstop' : channel.create('loadstop'),
        'loaderror' : channel.create('loaderror'),
        'exit' : channel.create('exit')
   };
}

OverAppBrowser.prototype = {
    _eventHandler: function (event) {
        if (event.type in this.channels) {
            this.channels[event.type].fire(event);
        }
    },
    close: function (eventname) {
        exec(null, null, "OverAppBrowser", "close", []);
    },
    show: function (eventname) {
        exec(null, null, "OverAppBrowser", "show", []);
    },
    fade: function (toAlpha, duration) {
        exec(null, null, "OverAppBrowser", "fade", [toAlpha, duration]);
    },
    resize: function (originx, originy, width, height) {
        exec(null, null, "OverAppBrowser", "resize", [originx, originy, width, height]);
    },
    addEventListener: function (eventname,f) {
        if (eventname in this.channels) {
            this.channels[eventname].subscribe(f);
        }
    },
    removeEventListener: function(eventname, f) {
        if (eventname in this.channels) {
            this.channels[eventname].unsubscribe(f);
        }
    },

    executeScript: function(injectDetails, cb) {
        if (injectDetails.code) {
            exec(cb, null, "OverAppBrowser", "injectScriptCode", [injectDetails.code, !!cb]);
        } else if (injectDetails.file) {
            exec(cb, null, "OverAppBrowser", "injectScriptFile", [injectDetails.file, !!cb]);
        } else {
            throw new Error('executeScript requires exactly one of code or file to be specified');
        }
    },

    insertCSS: function(injectDetails, cb) {
        if (injectDetails.code) {
            exec(cb, null, "OverAppBrowser", "injectStyleCode", [injectDetails.code, !!cb]);
        } else if (injectDetails.file) {
            exec(cb, null, "OverAppBrowser", "injectStyleFile", [injectDetails.file, !!cb]);
        } else {
            throw new Error('insertCSS requires exactly one of code or file to be specified');
        }
    }
};

module.exports = function(strUrl, originx,originy,width,height, isAutoFadeIn) {
    isAutoFadeIn = isAutoFadeIn || true;
    strUrl = urlutil.makeAbsolute(strUrl);
    var oab = new OverAppBrowser();
    var cb = function(eventname) {
        if (!eventname) {
            return;
        }
       oab._eventHandler(eventname);
    };

    exec(cb, cb, "OverAppBrowser", "open", [strUrl, originx, originy, width, height, isAutoFadeIn]);
    return oab;
};
<|endoftext|>


<commit_before>CT.map.DOMMarker = CT.Class({
	CLASSNAME: "CT.map.DOMMarker",
	setTitle: function(title) {
		this.opts.title = title;
		if (this.title)
			this.title.setContent(title);
		else
			this._build();
	},
	setIcon: function(icon) {
		if (typeof icon == "string")
			icon = CT.dom.img(icon);
		this.setContent(icon);
	},
	setMap: function(map) {
		this.opts.map = map;
		this.overlay.setMap(map);
	},
	_build: function() {
		if (this.opts.title) {
			this.title = new CT.map.Node(CT.merge(this.opts.hover, {
				map: this.opts.map,
				content: this.opts.title,
				position: this.getPosition(),
				className: "tooltip"
			}));
		}
	},
	init: function() {
		this.opts = CT.merge(this.opts, {
			className: "abs pointer"
		});
		this.addUpdater("map", this.setMap);
		this.addUpdater("icon", this.setIcon);
		this.addUpdater("title", this.setTitle);
		this._build();
	}
}, CT.map.Node);

CT.map.Marker = CT.Class({
	CLASSNAME: "CT.map.Marker",
	add: function(map) {
		this.opts.map = map;
		this.marker.setMap(map);
	},
	remove: function() {
		this.opts.map = null;
		this.marker.setMap(null);
	},
	update: function(opts) {
		this.opts = CT.merge(opts, this.opts);
	},
	showInfo: function() {
		var iw = this._infoWindow = this._infoWindow
			|| new google.maps.InfoWindow();
		iw.setContent(this.opts.info);
		iw.setPosition(this.marker.getPosition());
		iw.open(this.opts.map);
	},
	hideInfo: function() {
		this._infoWindow.close();
	},
	_addMarkerListener:  function(evt, cb) { // gmMarker only
		google.maps.event.addListener(this.marker, evt, cb);
	},
	_buildMarker: function() {
		this.marker = new google.maps.Marker(this.opts);
		for (var evt in this.opts.listeners)
			this._addMarkerListener(evt, this.opts.listeners[evt]);
	},
	_buildCustomMarker: function() {
		this.marker = new CT.map.DOMMarker(this.opts);
	},
	init: function(opts) {
		// required: position{lat,lng}
		// suggested: title"", map
		// if content, build custom dom marker
		this.opts = opts = CT.merge(opts, {
			icon: "http://maps.google.com/mapfiles/ms/icons/purple-dot.png",
			listeners: opts.info && { click: this.showInfo } || {}
		});
		opts.content ? this._buildCustomMarker() : this._buildMarker();
		if (this.opts.map)
			this.add(this.opts.map);
	}
});
<commit_msg>CT.map.Marker: wrap underlying getters and setters (title, icon, map, content, position, visible), converting values as necessary<commit_after>CT.map.DOMMarker = CT.Class({
	CLASSNAME: "CT.map.DOMMarker",
	setTitle: function(title) {
		this.opts.title = title;
		if (this.title)
			this.title.setContent(title);
		else
			this._build();
	},
	setIcon: function(icon) {
		if (typeof icon == "string")
			icon = CT.dom.img(icon);
		this.setContent(icon);
	},
	setMap: function(map) {
		this.opts.map = map;
		this.overlay.setMap(map);
	},
	_build: function() {
		if (this.opts.title) {
			this.title = new CT.map.Node(CT.merge(this.opts.hover, {
				map: this.opts.map,
				content: this.opts.title,
				position: this.getPosition(),
				className: "tooltip"
			}));
		}
	},
	init: function() {
		this.opts = CT.merge(this.opts, {
			className: "abs pointer"
		});
		this.addUpdater("map", this.setMap);
		this.addUpdater("icon", this.setIcon);
		this.addUpdater("title", this.setTitle);
		this._build();
	}
}, CT.map.Node);

CT.map.Marker = CT.Class({
	CLASSNAME: "CT.map.Marker",
	_converters: {
		position: CT.map.latlng
	},
	add: function(map) {
		this.opts.map = map;
		this.marker.setMap(map);
	},
	remove: function() {
		this.opts.map = null;
		this.marker.setMap(null);
	},
	update: function(opts) {
		this.opts = CT.merge(opts, this.opts);
	},
	showInfo: function() {
		var iw = this._infoWindow = this._infoWindow
			|| new google.maps.InfoWindow();
		iw.setContent(this.opts.info);
		iw.setPosition(this.getPosition());
		iw.open(this.opts.map);
	},
	hideInfo: function() {
		this._infoWindow.close();
	},
	_wrap: function(k) {
		var m = this, kup = CT.parse.capitalize(k);
		this["get" + kup] = function() {
			return m.opts[k];
		};
		this["set" + kup] = function(val) {
			m.opts[k] = m._converters[k] ? m._converters[k](val) : val;
			m.marker["set" + kup](m.opts[k]);
		};
	},
	_buildWrappers: function() {
		["title", "icon", "map", "content", "position", "visible"].forEach(this._wrap);
	},
	_addMarkerListener:  function(evt, cb) { // gmMarker only
		google.maps.event.addListener(this.marker, evt, cb);
	},
	_buildMarker: function() {
		this.marker = new google.maps.Marker(this.opts);
		for (var evt in this.opts.listeners)
			this._addMarkerListener(evt, this.opts.listeners[evt]);
	},
	_buildCustomMarker: function() {
		this.marker = new CT.map.DOMMarker(this.opts);
	},
	init: function(opts) {
		// required: position{lat,lng}
		// suggested: title"", map
		// if content, build custom dom marker
		this.opts = opts = CT.merge(opts, {
			icon: "http://maps.google.com/mapfiles/ms/icons/purple-dot.png",
			listeners: opts.info && { click: this.showInfo } || {}
		});
		opts.content ? this._buildCustomMarker() : this._buildMarker();
		this._buildWrappers();
		if (this.opts.map)
			this.add(this.opts.map);
	}
});
<|endoftext|>