<commit_before>import Color from 'color';

import {Platform} from 'react-native';

export default {

    // Badge
    badgeBg: '#ED1727',
    badgeColor: '#fff',

    // Button
    btnFontFamily: (Platform.OS === 'ios' ) ? 'HelveticaNeue' : 'Roboto_medium',
    btnDisabledBg: '#b5b5b5',
    btnDisabledClr: '#f1f1f1',

    get btnPrimaryBg () {
        return this.brandPrimary;
    },
    get btnPrimaryColor () {
        return this.inverseTextColor;
    },
    get btnInfoBg () {
        return this.brandInfo;
    },
    get btnInfoColor () {
        return this.inverseTextColor;
    },
    get btnSuccessBg () {
        return this.brandSuccess;
    },
    get btnSuccessColor () {
        return this.inverseTextColor;
    },
    get btnDangerBg () {
        return this.brandDanger;
    },
    get btnDangerColor () {
        return this.inverseTextColor;
    },
    get btnWarningBg () {
        return this.brandWarning;
    },
    get btnWarningColor () {
        return this.inverseTextColor;
    },
    get btnTextSize () {
        return (Platform.OS==='ios') ? this.fontSizeBase* 1.1 :
        this.fontSizeBase-1;
    },
    get btnTextSizeLarge () {
        return this.fontSizeBase* 1.5;
    },
    get btnTextSizeSmall () {
        return this.fontSizeBase* .8;
    },
    get borderRadiusLarge () {
        return this.fontSizeBase* 3.8;
    },

    buttonPadding: 6,

    get iconSizeLarge () {
        return this.iconFontSize* 1.5;
    },
    get iconSizeSmall () {
        return this.iconFontSize* .6;
    },

    // Card
    cardDefaultBg: '#fff',

    // Check Box
    checkboxBgColor: '#039BE5',
    checkboxSize: 23,
    checkboxTickColor: '#fff',

    // Color
    brandPrimary : '#5067FF',
    brandInfo: '#5bc0de',
    brandSuccess: '#5cb85c',
    brandDanger: '#d9534f',
    brandWarning: '#f0ad4e',
    brandSidebar: '#252932',

    // Font
    fontFamily: (Platform.OS === 'ios' ) ? 'HelveticaNeue' : 'Roboto',
    fontSizeBase: 15,

    get fontSizeH1 () {
        return this.fontSizeBase*1.8;
    },
    get fontSizeH2 () {
        return this.fontSizeBase* 1.6;
    },
    get fontSizeH3 () {
        return this.fontSizeBase* 1.4;
    },

    // Footer
    footerHeight: 55,
    footerDefaultBg: (Platform.OS === 'ios' ) ? '#F8F8F8' : '#4179F7',

    //FooterTab
    tabBarTextColor: (Platform.OS === 'ios' ) ? '#6b6b6b' : '#b3c7f9',
    tabBarActiveTextColor: (Platform.OS === 'ios' ) ? '#007aff' : '#fff',
    tabActiveBgColor: (Platform.OS=='ios') ? '#e4f0fe' : undefined,

    // Header
    iosToolbarBtnColor: '#007aff',
    toolbarDefaultBg: (Platform.OS === 'ios' ) ? '#F8F8F8' : '#4179F7',
    toolbarHeight: (Platform.OS === 'ios' ) ? 64 : 56,
    toolbarIconSize: (Platform.OS === 'ios' ) ? 20 : 22,
    toolbarInputColor: '#CECDD2',
    toolbarInverseBg: '#222',
    toolbarTextColor: (Platform.OS==='ios') ? '#000' : '#fff',
    get statusBarColor() {
        return Color(this.toolbarDefaultBg).darken(0.2).hexString();
    },

    // Icon
    iconFamily: 'Ionicons',
    iconFontSize: (Platform.OS === 'ios' ) ? 30 : 28,
    iconMargin: 7,

    // InputGroup
    inputFontSize: 15,
    inputBorderColor: '#D9D5DC',
    inputSuccessBorderColor: '#2b8339',
    inputErrorBorderColor: '#ed2f2f',

    get inputColor () {
        return this.textColor;
    },
    get inputColorPlaceholder () {
        return '#575757';
    },

    inputGroupMarginBottom: 10,
    inputHeightBase: 40,
    inputPaddingLeft: 5,

    get inputPaddingLeftIcon () {
        return this.inputPaddingLeft* 8;
    },

    // Line Height
    btnLineHeight: 19,
    lineHeightH1: 32,
    lineHeightH2: 27,
    lineHeightH3: 22,
    iconLineHeight: (Platform.OS === 'ios' ) ? 37 : 30,
    lineHeight: (Platform.OS === 'ios' ) ? 20 : 24,

    // List
    listBorderColor: '#ddd',
    listDividerBg: '#ddd',
    listItemHeight: 45,
    listItemPadding: 9,
    listNoteColor: '#808080',
    listNoteSize: 13,

    // Progress Bar
    defaultProgressColor: '#E4202D',
    inverseProgressColor: '#1A191B',

    // Radio Button
    radioBtnSize: (Platform.OS === 'ios') ? 25 : 23,
    radioColor: '#7e7e7e',

    get radioSelectedColor() {
        return Color(this.radioColor).darken(0.2).hexString();
    },

    // Spinner
    defaultSpinnerColor: '#45D56E',
    inverseSpinnerColor: '#1A191B',

    // Tabs
    tabBgColor: '#F8F8F8',
    tabFontSize: 15,
    tabTextColor: '#fff',

    // Text
    textColor: '#000',
    inverseTextColor: '#fff',

    // Title
    titleFontSize: (Platform.OS === 'ios' ) ? 17 : 19,
    subTitleFontSize: (Platform.OS === 'ios' ) ? 12 : 14,
    subtitleColor: '#8e8e93',

    // Other
    borderRadiusBase: (Platform.OS === 'ios' ) ? 5 : 2,
    borderWidth: 1,
    contentPadding: 10,

    get darkenHeader() {
        return Color(this.tabBgColor).darken(0.03).hexString();
    },

    dropdownBg: '#000',
    dropdownLinkColor: '#414142',
    inputLineHeight: 24,
    jumbotronBg: '#C9C9CE',
    jumbotronPadding: 30
}
<commit_msg>Update light.js<commit_after>import Color from 'color';

import {Platform} from 'react-native';

export default {

    // Badge
    badgeBg: '#ED1727',
    badgeColor: '#fff',

    // Button
    btnFontFamily: (Platform.OS === 'ios' ) ? 'HelveticaNeue' : 'Roboto_medium',
    btnDisabledBg: '#b5b5b5',
    btnDisabledClr: '#f1f1f1',

    get btnPrimaryBg () {
        return this.brandPrimary;
    },
    get btnPrimaryColor () {
        return this.inverseTextColor;
    },
    get btnInfoBg () {
        return this.brandInfo;
    },
    get btnInfoColor () {
        return this.inverseTextColor;
    },
    get btnSuccessBg () {
        return this.brandSuccess;
    },
    get btnSuccessColor () {
        return this.inverseTextColor;
    },
    get btnDangerBg () {
        return this.brandDanger;
    },
    get btnDangerColor () {
        return this.inverseTextColor;
    },
    get btnWarningBg () {
        return this.brandWarning;
    },
    get btnWarningColor () {
        return this.inverseTextColor;
    },
    get btnTextSize () {
        return (Platform.OS==='ios') ? this.fontSizeBase* 1.1 :
        this.fontSizeBase-1;
    },
    get btnTextSizeLarge () {
        return this.fontSizeBase* 1.5;
    },
    get btnTextSizeSmall () {
        return this.fontSizeBase* .8;
    },
    get borderRadiusLarge () {
        return this.fontSizeBase* 3.8;
    },

    buttonPadding: 6,

    get iconSizeLarge () {
        return this.iconFontSize* 1.5;
    },
    get iconSizeSmall () {
        return this.iconFontSize* .6;
    },

    // Card
    cardDefaultBg: '#fff',

    // Check Box
    checkboxBgColor: '#039BE5',
    checkboxSize: 23,
    checkboxTickColor: '#fff',

    // Color
    brandPrimary : '#5067FF',
    brandInfo: '#5bc0de',
    brandSuccess: '#5cb85c',
    brandDanger: '#d9534f',
    brandWarning: '#f0ad4e',
    brandSidebar: '#252932',

    // Font
    fontFamily: (Platform.OS === 'ios' ) ? 'HelveticaNeue' : 'Roboto',
    fontSizeBase: 15,

    get fontSizeH1 () {
        return this.fontSizeBase*1.8;
    },
    get fontSizeH2 () {
        return this.fontSizeBase* 1.6;
    },
    get fontSizeH3 () {
        return this.fontSizeBase* 1.4;
    },

    // Footer
    footerHeight: 55,
    footerDefaultBg: (Platform.OS === 'ios' ) ? '#F8F8F8' : '#4179F7',

    //FooterTab
    tabBarTextColor: (Platform.OS === 'ios' ) ? '#6b6b6b' : '#b3c7f9',
    tabBarActiveTextColor: (Platform.OS === 'ios' ) ? '#007aff' : '#fff',
    tabActiveBgColor: (Platform.OS=='ios') ? '#e4f0fe' : undefined,

    // Header
    iosToolbarBtnColor: '#007aff',
    toolbarDefaultBg: (Platform.OS === 'ios' ) ? '#F8F8F8' : '#4179F7',
    toolbarHeight: (Platform.OS === 'ios' ) ? 64 : 56,
    toolbarIconSize: (Platform.OS === 'ios' ) ? 20 : 22,
    toolbarInputColor: '#CECDD2',
    toolbarInverseBg: '#222',
    toolbarTextColor: (Platform.OS==='ios') ? '#000' : '#fff',
    get statusBarColor() {
        return Color(this.toolbarDefaultBg).darken(0.2).hexString();
    },

    // Icon
    iconFamily: 'Ionicons',
    iconFontSize: (Platform.OS === 'ios' ) ? 30 : 28,
    iconMargin: 7,

    // InputGroup
    inputFontSize: 15,
    inputBorderColor: '#D9D5DC',
    inputSuccessBorderColor: '#2b8339',
    inputErrorBorderColor: '#ed2f2f',

    get inputColor () {
        return this.textColor;
    },
    get inputColorPlaceholder () {
        return '#575757';
    },

    inputGroupMarginBottom: 10,
    inputHeightBase: 40,
    inputPaddingLeft: 5,

    get inputPaddingLeftIcon () {
        return this.inputPaddingLeft* 8;
    },

    // Line Height
    btnLineHeight: 19,
    lineHeightH1: 32,
    lineHeightH2: 27,
    lineHeightH3: 22,
    iconLineHeight: (Platform.OS === 'ios' ) ? 37 : 30,
    lineHeight: (Platform.OS === 'ios' ) ? 20 : 24,

    // List
    listBorderColor: '#ddd',
    listDividerBg: '#ddd',
    listItemHeight: 45,
    listItemPadding: 9,
    listNoteColor: '#808080',
    listNoteSize: 13,

    // Progress Bar
    defaultProgressColor: '#E4202D',
    inverseProgressColor: '#1A191B',

    // Radio Button
    radioBtnSize: (Platform.OS === 'ios') ? 25 : 23,
    radioColor: '#7e7e7e',

    get radioSelectedColor() {
        return Color(this.radioColor).darken(0.2).hexString();
    },

    // Spinner
    defaultSpinnerColor: '#45D56E',
    inverseSpinnerColor: '#1A191B',

    // Tabs
    tabBgColor: '#F8F8F8',
    tabFontSize: 15,
    tabTextColor: '#fff',

    // Text
    textColor: '#000',
    inverseTextColor: '#fff',

    // Title
    titleFontSize: (Platform.OS === 'ios' ) ? 17 : 19,
    subTitleFontSize: (Platform.OS === 'ios' ) ? 12 : 14,
    subtitleColor: '#8e8e93',

    // Other
    borderRadiusBase: (Platform.OS === 'ios' ) ? 5 : 2,
    borderWidth: 1,
    contentPadding: 10,

    get darkenHeader() {
        return Color(this.tabBgColor).darken(0.03).hexString();
    },

    dropdownBg: '#000',
    dropdownLinkColor: '#414142',
    inputLineHeight: 24,
    jumbotronBg: '#C9C9CE',
    jumbotronPadding: 30
}
<|endoftext|>


<commit_before>const path = require('path')
const i18n = require('../..')
const one = require('./modules/one')
const two = require('./modules/two')

i18n.configure({
  locales: ['en', 'de'],
  directory: path.join(__dirname, 'locales')
})

// set to german
i18n.setLocale('de')

// will put 'Hallo'
console.log('index.js', i18n.__('Hello'))

// will also put 'Hallo'
one()

// will also put 'Hallo'
two()

// -------------------------------------------------

// set to german
i18n.setLocale('en')

// will put 'Hello'
console.log('index.js', i18n.__('Hello'))

// will also put 'Hello'
one()

// will also put 'Hello'
two()
<commit_msg>Fix comment<commit_after>const path = require('path')
const i18n = require('../..')
const one = require('./modules/one')
const two = require('./modules/two')

i18n.configure({
  locales: ['en', 'de'],
  directory: path.join(__dirname, 'locales')
})

// set to german
i18n.setLocale('de')

// will put 'Hallo'
console.log('index.js', i18n.__('Hello'))

// will also put 'Hallo'
one()

// will also put 'Hallo'
two()

// -------------------------------------------------

// set to english
i18n.setLocale('en')

// will put 'Hello'
console.log('index.js', i18n.__('Hello'))

// will also put 'Hello'
one()

// will also put 'Hello'
two()
<|endoftext|>


<commit_before>Ext.require( [ 'Ext.window.*', 'Ext.layout.container.Border' ] );

Ext.define( 'ASPIREdb.view.PhenotypesContigencyTableWindow', {
   extend : 'Ext.Window',
   alias : 'widget.phenotypescontigencytablewindow',
   singleton : true,
   title : 'Contingency Table',
   closable : true,
   closeAction : 'hide',
   width : 900,
   height : 450,
   layout : 'fit',
   bodyStyle : 'padding: 5px;',
   config : {
      selectedSubjectIds : [],
      selSubjects : [],
      visibleLabels : [],
      gridPanelName : '',

   },

   initComponent : function() {

      this.callParent();

   },

   initGridAndShow : function(psvos, selPhenotypes) {
      if ( selPhenotypes.length > 3 ) {
         Ext.Msg.alert( 'Warning', 'User is allowed to select maximum 3 phenotypes to view thw table. Reselect' );
      } else {
         // if only one phenotype is selected
         if ( selPhenotypes.length == 1 ) {
            var data = [];

            var phenSummary = selPhenotypes[0].data.selectedPhenotype;
            var phenotypeName = phenSummary.name;
            var columnNames = [ phenotypeName, 'Subject Count' ];

            for ( var labelName in phenSummary.phenoSummaryMap) {
               var subjects = [];
               if ( labelName != "Unknown" ) {
                  if ( labelName == "Present" )
                     subjects = phenSummary.subjects[1];
                  else if ( labelName == "Absent" )
                     subjects = phenSummary.subjects[0];
                  else if ( labelName == "Y" )
                     subjects = phenSummary.subjects['Y'];
                  else if ( labelName == "N" )
                     subjects = phenSummary.subjects['N'];
                  else
                     subjects = phenSummary.subjects[labelName];
                  // rowName, cell Value
                  var row = [ labelName, subjects ];
                  data.push( row );
               }
            }
            this.createGridPanel( data, columnNames, phenotypeName );

         } else {
            // multiple cases
            var selectedPhenotypeData = [];
            var phenotypeName = '';
            var columnNames = [];
            for (var i = 0; i < selPhenotypes.length; i++) {
               var phenSummary = selPhenotypes[i].data.selectedPhenotype;

               phenotypeName = phenotypeName + phenSummary.name;

               console.log( 'grid panel names: ' + phenotypeName );
               var pheneData = [];

               columnNames.push( phenSummary.name );

               for ( var rowlabelName in phenSummary.phenoSummaryMap) {
                  var subjects = [];
                  if ( rowlabelName != "Unknown" ) {
                     if ( rowlabelName == "Present" )
                        subjects = phenSummary.subjects[1];
                     else if ( rowlabelName == "Absent" )
                        subjects = phenSummary.subjects[0];
                     else if ( rowlabelName == "Y" )
                        subjects = phenSummary.subjects['Y'];
                     else if ( rowlabelName == "N" )
                        subjects = phenSummary.subjects['N'];
                     else
                        subjects = phenSummary.subjects[rowlabelName];
                     pheneData.push( [ rowlabelName, subjects ] );
                  }
               }
               selectedPhenotypeData.push( pheneData );
            }

            var resultantData = selectedPhenotypeData[0];
            for (var i = 1; i < selectedPhenotypeData.length; i++) {
               var newResultantRow = [];
               for (var j = 0; j < resultantData.length; j++) {
                  for (var m = 0; m < selectedPhenotypeData[i].length; m++) {
                     var columnValues = resultantData[j];
                     var newResultantColumn = [];
                     // push the phenotype values for the grid column
                     for (var k = 0; k < columnValues.length - 1; k++) {
                        if ( columnValues[k] != 'transpose' ) {
                           newResultantColumn.push( columnValues[k] );
                        }
                     }
                     // newResultantColumn.push( selectedPhenotypeData[i][0][m] );
                     var secondColumnValues = selectedPhenotypeData[i][m];
                     for (var n = 0; n < secondColumnValues.length - 1; n++) {
                        if ( secondColumnValues[k] != 'transpose' ) {
                           newResultantColumn.push( secondColumnValues[n] );
                        }
                     }

                     // finding the common subject ids for selected 2 phenotypes
                     var firstColumnSubjects = columnValues[columnValues.length - 1];
                     var secondColumnSubjects = secondColumnValues[secondColumnValues.length - 1];
                     var resultSubjects = [];
                     for (var k = 0; k < firstColumnSubjects.length; k++) {
                        if ( secondColumnSubjects.indexOf( firstColumnSubjects[k] ) != -1 ) {
                           resultSubjects.push( firstColumnSubjects[k] );
                        }
                     }

                     newResultantColumn.push( resultSubjects );
                     newResultantRow.push( newResultantColumn );

                  }

               }
               // recursive resultant data
               resultantData = newResultantRow;

            }
            columnNames.push( 'Subject Count' );
          //  columnNames.push( '' );
            this.createGridPanel( resultantData, columnNames, phenotypeName );

         }

         this.show();
      }

   },

   createGridPanel : function(data, columnNames, phenotypeName) {

      var ref = this;
      ref.removeAll();
      ref.gridPanelName = phenotypeName;
      var fields = [];

      if ( columnNames.length > 0 ) {
         for (var i = 0; i < columnNames.length; i++) {
            fields.push( columnNames[i] );
         }
      }
      // create store
      var suggestContigencyTableStore = Ext.create( 'Ext.data.ArrayStore', {
         fields : fields,
         data : data,
         autoLoad : true,
         autoSync : true,
         storeId : phenotypeName,

      } );

      // columns
      var columns = [];

      if ( columnNames.length > 1 ) {
         for (var i = 0; i < columnNames.length; i++) {
            if ( columnNames[i] == 'Subject Count' ) {
               columns.push( {
                  header : columnNames[i],
                  dataIndex : columnNames[i],
                  width : 100,
                  renderer : function(value) {
                     var src = 'scripts/ASPIREdb/resources/images/icons/tag.png';
                     var tooltip = "Click to add label";

                     var ahrefurl = '';

                     image = Ext.String.format( ahrefurl + " ", src, tooltip );
                     //

                     var ret = image+'&nbsp&nbsp'+value.length;
                     if (value.length==0) ret ='&nbsp&nbsp'+value.length;
                     return ret;
                     //return value.length
                  }
               } );
            } /**else if ( columnNames[i] == '' ) {
               columns.push( {
                  header : 'Label',
                  dataIndex : 'tag',
                  width : 50,
                  renderer : function() {

                     var src = 'scripts/ASPIREdb/resources/images/icons/tag.png';
                     var tooltip = "Click to add label";

                     var ahrefurl = '';

                     image = Ext.String.format( ahrefurl + " ", src, tooltip );

                     var ret = image;
                     return ret;

                  }

               } );
            } */else {
               columns.push( {
                  header : columnNames[i],
                  dataIndex : columnNames[i],
                  flex : 1,

               } );
            }

         }
      }
      // create grid
      var grid = Ext.create( 'Ext.grid.Panel', {
         id : phenotypeName,
         closable : false,
         border : true,
         store : suggestContigencyTableStore,
         columns : columns,
         autoRender : true,
         // multiSelect : false,
         width : 850,
         listeners : {
            cellclick : function(view, td, cellIndex, record, tr, rowIndex, e, eOpts) {

               var subjectIdLength = record.raw.length;
               ref.selectedSubjectIds = record.raw[subjectIdLength - 1];

               // Stop the browser getting the event
              // e.preventDefault();
               ref.makeLabelHandler();

             /**  var contextMenu = new Ext.menu.Menu( {

                  items : [ {
                     text : 'Make label',
                     handler : ref.makeLabelHandler,
                     scope : ref,
                  } ]
               } );

               contextMenu.showAt( e.getX(), e.getY() );*/
            }

         // }
         },

         selModel : Ext.create( 'Ext.selection.RowModel', {
            mode : 'MULTI',
            listeners : {
               click : {
                  element : 'el', // bind to the underlying el property on the panel
                  fn : function() {
                     console.log( 'click el' );

                  }
               },
            }
         } ),

      } );

      ref.add( grid );
      ref.show();
   },
   /**
    * Reusing the code in subject grid Assigns a Label
    *
    * @param :
    *           event
    */
   makeLabelHandler : function() {

      var me = this;

      var subjectIds = me.selectedSubjectIds;
      var projectIds = ASPIREdb.ActiveProjectSettings.getActiveProjectIds();
      SubjectService.getSubjects( projectIds[0], subjectIds, {
         callback : function(selectedSubjectValueObjects) {
            me.selSubjects = selectedSubjectValueObjects;
         }
      } );

      Ext.define( 'ASPIREdb.view.CreateLabelWindowSubject', {
         isSubjectLabel : true,
         extend : 'ASPIREdb.view.CreateLabelWindow',

         // override
         onOkButtonClick : function() {

            var labelCombo = this.down( "#labelCombo" );
            var vo = this.getLabel();
            if ( vo == null ) {
               return;
            }
            var labelIndex = labelCombo.getStore().findExact( 'display', vo.name );
            if ( labelIndex != -1 ) {
               // activate confirmation window
               Ext.MessageBox.confirm( 'Label already exist', 'Label already exist. Add into it ?', function(btn) {
                  if ( btn === 'yes' ) {
                     me.addLabelHandler( vo, subjectIds );
                     this.hide();

                  }

               }, this );

            } else {
               me.addLabelHandler( vo, subjectIds );
               this.hide();

            }

         }
      } );

      var labelWindow = new ASPIREdb.view.CreateLabelWindowSubject();
      labelWindow.show();
      ASPIREdb.EVENT_BUS.fireEvent( 'phenotype_label_created' );

   },

   /**
    * Reusing the code in subject grid Add the label to the store
    *
    * @param: label value object, selected subject Ids
    */
   addLabelHandler : function(vo, selSubjectIds) {

      var me = this;

      // store in database
      SubjectService.addLabel( selSubjectIds, vo, {
         callback : function(addedLabel) {

            addedLabel.isShown = true;
            LabelService.updateLabel( addedLabel );

            var existingLab = me.visibleLabels[addedLabel.id];
            if ( existingLab == undefined ) {
               me.visibleLabels[addedLabel.id] = addedLabel;
            } else {
               existingLab.isShown = true;
            }
            for (var i = 0; i < me.selSubjects.length; i++) {
               me.selSubjects[i].labels.push( addedLabel );
            }

            ASPIREdb.EVENT_BUS.fireEvent( 'subject_label_changed',selSubjectIds );

         }
      } );

   },

   /**
    * Reusing the code in subject grid to Load subject labels created by the user
    *
    * @return visibleLabels
    */
   createVisibleLabels : function() {
      var visibleLabels = [];
      var suggestionContext = new SuggestionContext();
      suggestionContext.activeProjectIds = ASPIREdb.ActiveProjectSettings.getActiveProjectIds();

      // load all labels created by this user
      SubjectService.suggestLabels( suggestionContext, {
         callback : function(labels) {
            for ( var idx in labels) {
               var label = labels[idx];
               visibleLabels[label.id] = label;
            }
         }
      } );

      return visibleLabels;
   },

} );<commit_msg>fix to contingengy table<commit_after>Ext.require( [ 'Ext.window.*', 'Ext.layout.container.Border' ] );

Ext.define( 'ASPIREdb.view.PhenotypesContigencyTableWindow', {
   extend : 'Ext.Window',
   alias : 'widget.phenotypescontigencytablewindow',
   singleton : true,
   title : 'Contingency Table',
   closable : true,
   closeAction : 'hide',
   width : 900,
   height : 450,
   layout : 'fit',
   bodyStyle : 'padding: 5px;',
   config : {
      selectedSubjectIds : [],
      selSubjects : [],
      visibleLabels : [],
      gridPanelName : '',

   },

   initComponent : function() {

      this.callParent();

   },

   initGridAndShow : function(psvos, selPhenotypes) {
      if ( selPhenotypes.length > 3 ) {
         Ext.Msg.alert( 'Warning', 'User is allowed to select maximum 3 phenotypes to view thw table. Reselect' );
      } else {
         // if only one phenotype is selected
         if ( selPhenotypes.length == 1 ) {
            var data = [];

            var phenSummary = selPhenotypes[0].data.selectedPhenotype;
            var phenotypeName = phenSummary.name;
            var columnNames = [ phenotypeName, 'Subject Count' ];

            for ( var labelName in phenSummary.phenoSummaryMap) {
               var subjects = [];
               if ( labelName != "Unknown" ) {
                  if ( labelName == "Present" )
                     subjects = phenSummary.subjects[1];
                  else if ( labelName == "Absent" )
                     subjects = phenSummary.subjects[0];
                  else if ( labelName == "Y" )
                     subjects = phenSummary.subjects['Y'];
                  else if ( labelName == "N" )
                     subjects = phenSummary.subjects['N'];
                  else
                     subjects = phenSummary.subjects[labelName];
                  // rowName, cell Value
                  var row = [ labelName, subjects ];
                  data.push( row );
               }
            }
            this.createGridPanel( data, columnNames, phenotypeName );

         } else {
            // multiple cases
            var selectedPhenotypeData = [];
            var phenotypeName = '';
            var columnNames = [];
            for (var i = 0; i < selPhenotypes.length; i++) {
               var phenSummary = selPhenotypes[i].data.selectedPhenotype;

               phenotypeName = phenotypeName + phenSummary.name;

               console.log( 'grid panel names: ' + phenotypeName );
               var pheneData = [];

               columnNames.push( phenSummary.name );

               for ( var rowlabelName in phenSummary.phenoSummaryMap) {
                  var subjects = [];
                  if ( rowlabelName != "Unknown" ) {
                     if ( rowlabelName == "Present" )
                        subjects = phenSummary.subjects[1];
                     else if ( rowlabelName == "Absent" )
                        subjects = phenSummary.subjects[0];
                     else if ( rowlabelName == "Y" )
                        subjects = phenSummary.subjects['Y'];
                     else if ( rowlabelName == "N" )
                        subjects = phenSummary.subjects['N'];
                     else
                        subjects = phenSummary.subjects[rowlabelName];
                     pheneData.push( [ rowlabelName, subjects ] );
                  }
               }
               selectedPhenotypeData.push( pheneData );
            }

            var resultantData = selectedPhenotypeData[0];
            for (var i = 1; i < selectedPhenotypeData.length; i++) {
               var newResultantRow = [];
               for (var j = 0; j < resultantData.length; j++) {
                  for (var m = 0; m < selectedPhenotypeData[i].length; m++) {
                     var columnValues = resultantData[j];
                     var newResultantColumn = [];
                     // push the phenotype values for the grid column
                     for (var k = 0; k < columnValues.length - 1; k++) {
                        if ( columnValues[k] != 'transpose' ) {
                           newResultantColumn.push( columnValues[k] );
                        }
                     }
                     // newResultantColumn.push( selectedPhenotypeData[i][0][m] );
                     var secondColumnValues = selectedPhenotypeData[i][m];
                     for (var n = 0; n < secondColumnValues.length - 1; n++) {
                        if ( secondColumnValues[k] != 'transpose' ) {
                           newResultantColumn.push( secondColumnValues[n] );
                        }
                     }

                     // finding the common subject ids for selected 2 phenotypes
                     var firstColumnSubjects = columnValues[columnValues.length - 1];
                     var secondColumnSubjects = secondColumnValues[secondColumnValues.length - 1];
                     var resultSubjects = [];
                     for (var k = 0; k < firstColumnSubjects.length; k++) {
                        if ( secondColumnSubjects.indexOf( firstColumnSubjects[k] ) != -1 ) {
                           resultSubjects.push( firstColumnSubjects[k] );
                        }
                     }

                     newResultantColumn.push( resultSubjects );
                     newResultantRow.push( newResultantColumn );

                  }

               }
               // recursive resultant data
               resultantData = newResultantRow;

            }
            columnNames.push( 'Subject Count' );
          //  columnNames.push( '' );
            this.createGridPanel( resultantData, columnNames, phenotypeName );

         }

         this.show();
      }

   },

   createGridPanel : function(data, columnNames, phenotypeName) {

      var ref = this;
      ref.removeAll();
      ref.gridPanelName = phenotypeName;
      var fields = [];

      if ( columnNames.length > 0 ) {
         for (var i = 0; i < columnNames.length; i++) {
            fields.push( columnNames[i] );
         }
      }
      // create store
      var suggestContigencyTableStore = Ext.create( 'Ext.data.ArrayStore', {
         fields : fields,
         data : data,
         autoLoad : true,
         autoSync : true,
         storeId : phenotypeName,

      } );

      // columns
      var columns = [];

      if ( columnNames.length > 1 ) {
         for (var i = 0; i < columnNames.length; i++) {
            if ( columnNames[i] == 'Subject Count' ) {
               columns.push( {
                  header : columnNames[i],
                  dataIndex : columnNames[i],
                  width : 100,
                  renderer : function(value) {
                     var src = 'scripts/ASPIREdb/resources/images/icons/tag.png';
                     var tooltip = "Click to add label";

                     var ahrefurl = '';

                     image = Ext.String.format( ahrefurl + " ", src, tooltip );
                     //

                     var ret = value.length+'&nbsp&nbsp'+image;
                     if (value.length==0) ret =value.length+'&nbsp&nbsp';
                     return ret;
                     //return value.length
                  }
               } );
            } else {
               columns.push( {
                  header : columnNames[i],
                  dataIndex : columnNames[i],
                  flex : 1,

               } );
            }

         }
      }
      // create grid
      var grid = Ext.create( 'Ext.grid.Panel', {
         id : phenotypeName,
         closable : false,
         border : true,
         store : suggestContigencyTableStore,
         columns : columns,
         autoRender : true,
         // multiSelect : false,
         width : 850,
         listeners : {
            cellclick : function(view, td, cellIndex, record, tr, rowIndex, e, eOpts) {

               var subjectIdLength = record.raw.length;
               ref.selectedSubjectIds = record.raw[subjectIdLength - 1];
               if (ref.selectedSubjectIds.length!=0)
                     ref.makeLabelHandler();

            }

         // }
         },

         selModel : Ext.create( 'Ext.selection.RowModel', {
            mode : 'MULTI',
            listeners : {
               click : {
                  element : 'el', // bind to the underlying el property on the panel
                  fn : function() {
                     console.log( 'click el' );

                  }
               },
            }
         } ),

      } );

      ref.add( grid );
      ref.show();
   },
   /**
    * Reusing the code in subject grid Assigns a Label
    *
    * @param :
    *           event
    */
   makeLabelHandler : function() {

      var me = this;

      var subjectIds = me.selectedSubjectIds;
      var projectIds = ASPIREdb.ActiveProjectSettings.getActiveProjectIds();
      SubjectService.getSubjects( projectIds[0], subjectIds, {
         callback : function(selectedSubjectValueObjects) {
            me.selSubjects = selectedSubjectValueObjects;
         }
      } );

      Ext.define( 'ASPIREdb.view.CreateLabelWindowSubject', {
         isSubjectLabel : true,
         extend : 'ASPIREdb.view.CreateLabelWindow',

         // override
         onOkButtonClick : function() {

            var labelCombo = this.down( "#labelCombo" );
            var vo = this.getLabel();
            if ( vo == null ) {
               return;
            }
            var labelIndex = labelCombo.getStore().findExact( 'display', vo.name );
            if ( labelIndex != -1 ) {
               // activate confirmation window
               Ext.MessageBox.confirm( 'Label already exist', 'Label already exist. Add into it ?', function(btn) {
                  if ( btn === 'yes' ) {
                     me.addLabelHandler( vo, subjectIds );
                     this.hide();

                  }

               }, this );

            } else {
               me.addLabelHandler( vo, subjectIds );
               this.hide();

            }

         }
      } );

      var labelWindow = new ASPIREdb.view.CreateLabelWindowSubject();
      labelWindow.show();
      ASPIREdb.EVENT_BUS.fireEvent( 'phenotype_label_created' );

   },

   /**
    * Reusing the code in subject grid Add the label to the store
    *
    * @param: label value object, selected subject Ids
    */
   addLabelHandler : function(vo, selSubjectIds) {

      var me = this;

      // store in database
      SubjectService.addLabel( selSubjectIds, vo, {
         callback : function(addedLabel) {

            addedLabel.isShown = true;
            LabelService.updateLabel( addedLabel );

            var existingLab = me.visibleLabels[addedLabel.id];
            if ( existingLab == undefined ) {
               me.visibleLabels[addedLabel.id] = addedLabel;
            } else {
               existingLab.isShown = true;
            }
            for (var i = 0; i < me.selSubjects.length; i++) {
               me.selSubjects[i].labels.push( addedLabel );
            }

            ASPIREdb.EVENT_BUS.fireEvent( 'subject_label_changed',selSubjectIds );

         }
      } );

   },

   /**
    * Reusing the code in subject grid to Load subject labels created by the user
    *
    * @return visibleLabels
    */
   createVisibleLabels : function() {
      var visibleLabels = [];
      var suggestionContext = new SuggestionContext();
      suggestionContext.activeProjectIds = ASPIREdb.ActiveProjectSettings.getActiveProjectIds();

      // load all labels created by this user
      SubjectService.suggestLabels( suggestionContext, {
         callback : function(labels) {
            for ( var idx in labels) {
               var label = labels[idx];
               visibleLabels[label.id] = label;
            }
         }
      } );

      return visibleLabels;
   },

} );<|endoftext|>


<commit_before>

/**
 * Helper function to flatten any string-language objects (Objects
 * with @value and @lang fields only) into an elasticsearch
 * friendly type
 *
 * @param jsonLd object to flatten
 * @returns flattened jsonLd object as String
 */
// @first is an internal parameter to indicate which call is not a recursive call (used to return a string object at the end)
function flattenStringFields(jsonLd, first){

	if(jsonLd == undefined){
		jsonLd = JSON.parse(this.params.obj);
		first = true;
	}

	if(Array.isArray(jsonLd)){
		var ret = [];

		for(idx in jsonLd){
			ret.push(flattenStringFields(jsonLd[idx], false));
		}

		return ret;
	}else if(jsonLd === Object(jsonLd)){
		for(var key in jsonLd){
			try{
				var field = jsonLd[key];

				// Check if objects in array are string-language objects
				if(Array.isArray(field)){
					var flattenText = true; // flag identifying if they are string-language objects
					var textArray = [];
					var langArray = [];
					for(var idx in field){
						var obj = field[idx];

						if(obj === Object(obj)){
							if(Object.keys(obj).length == 2 && obj["@value"] != undefined && obj["@language"] != undefined){
								textArray.push(obj["@value"]);
								langArray.push(obj["@language"]);
							}else{
								flattenText = false
							}
						}else{
							flattenText = false;
						}
					}
					// if not string-language objects keep recursing
					if(!flattenText){
						jsonLd[key] = flattenStringFields(field, false);
					// otherwise flatten @value to field and create <key>@language field
					}else{
						jsonLd[key] = textArray;
						jsonLd[key+"@language"] = langArray;
					}
				// Check if object is a string-language object
				}else if(field === Object(field)){
					// if string-language object flatten @value to field and create <key>@language field
					if(Object.keys(field).length == 2 && field["@value"] != undefined && field["@language"] != undefined){
						jsonLd[key] = field["@value"];
						jsonLd[key+"@language"] = field["@language"];
					// otherwise keep recursing
					}else if (Object.keys(field).length > 0){
						jsonLd[key] = flattenStringFields(field, false);
					}
				}
			}catch(exception){
				error("Exception when flattening json-ld text fields: "+exception.message)
			}
		}
	}

	if(first != undefined && first == false){
		return jsonLd;
	}else{
		return JSON.stringify(jsonLd)
	}
}<commit_msg>Preventing registered objects from getting @language addendums infinitely.<commit_after>

/**
 * Helper function to flatten any string-language objects (Objects
 * with @value and @lang fields only) into an elasticsearch
 * friendly type
 *
 * @param jsonLd object to flatten
 * @returns flattened jsonLd object as String
 */
// @first is an internal parameter to indicate which call is not a recursive call (used to return a string object at the end)
function flattenStringFields(jsonLd, first){

	if(jsonLd == undefined){
		jsonLd = JSON.parse(this.params.obj);
		first = true;
	}

	if(Array.isArray(jsonLd)){
		var ret = [];

		for(idx in jsonLd){
			ret.push(flattenStringFields(jsonLd[idx], false));
		}

		return ret;
	}else if(jsonLd === Object(jsonLd)){
		for(var key in jsonLd){
			try{
				var field = jsonLd[key];

				// Check if objects in array are string-language objects
				if(Array.isArray(field) && field.length > 0){
					var flattenText = true; // flag identifying if they are string-language objects
					var textArray = [];
					var langArray = [];
					for(var idx in field){
						var obj = field[idx];

						if(obj === Object(obj)){
							if(Object.keys(obj).length == 2 && obj["@value"] != undefined && obj["@language"] != undefined){
								textArray.push(obj["@value"]);
								langArray.push(obj["@language"]);
							}else{
								flattenText = false
							}
						}else{
							flattenText = false;
						}
					}
					// if not string-language objects keep recursing
					if(!flattenText){
						jsonLd[key] = flattenStringFields(field, false);
					// otherwise flatten @value to field and create <key>@language field
					}else{
						jsonLd[key] = textArray;
						jsonLd[key+"@language"] = langArray;
					}
				// Check if object is a string-language object
				}else if(field === Object(field)){
					// if string-language object flatten @value to field and create <key>@language field
					if(Object.keys(field).length == 2 && field["@value"] != undefined && field["@language"] != undefined){
						jsonLd[key] = field["@value"];
						jsonLd[key+"@language"] = field["@language"];
					// otherwise keep recursing
					}else if (Object.keys(field).length > 0){
						jsonLd[key] = flattenStringFields(field, false);
					}
				}
			}catch(exception){
				error("Exception when flattening json-ld text fields: "+exception.message)
			}
		}
	}

	if(first != undefined && first == false){
		return jsonLd;
	}else{
		return JSON.stringify(jsonLd)
	}
}

<|endoftext|>


<commit_before>#!/usr/bin/env node
import '../../server/env';

import config from 'config';
import { parse as json2csv } from 'json2csv';
import { entries, groupBy, pick, round, sumBy } from 'lodash';
import moment from 'moment';

import expenseStatus from '../../server/constants/expense_status';
import expenseTypes from '../../server/constants/expense_type';
import plans, { SHARED_REVENUE_PLANS } from '../../server/constants/plans';
import { SETTLEMENT_EXPENSE_PROPERTIES, TransactionTypes } from '../../server/constants/transactions';
import { uploadToS3 } from '../../server/lib/awsS3';
import { generateKey } from '../../server/lib/encryption';
import models, { sequelize } from '../../server/models';
import { PayoutMethodTypes } from '../../server/models/PayoutMethod';

// Only run on the 5th of the month
const date = process.env.START_DATE ? moment.utc(process.env.START_DATE) : moment.utc();
const DRY = process.env.DRY;
const HOST_ID = process.env.HOST_ID;
const isProduction = config.env === 'production';
if (isProduction && date.date() !== 1 && !process.env.OFFCYCLE) {
  console.log('OC_ENV is production and today is not the 5th of month, script aborted!');
  process.exit();
}
if (DRY) {
  console.info('Running dry, changes are not going to be persisted to the DB.');
}

const ATTACHED_CSV_COLUMNS = [
  'createdAt',
  'description',
  'CollectiveSlug',
  'amount',
  'currency',
  'OrderId',
  'TransactionId',
  'PaymentService',
  'source',
];

export async function run() {
  console.info(`Invoicing hosts pending fees and tips for ${moment(date).subtract(1, 'month').format('MMMM')}.`);
  const [pastMonthTransactions] = await sequelize.query(
    `
    WITH "platformTips" AS (
      SELECT
        t."createdAt",
        t.description,
        round(t."netAmountInCollectiveCurrency"::float / COALESCE((t."data"->>'hostToPlatformFxRate')::float, 1)) AS "amount",
        ot."hostCurrency" AS "currency",
        ot."CollectiveId",
        c."slug" AS "CollectiveSlug",
        ot."HostCollectiveId",
        h."name" AS "HostName",
        ot."OrderId",
        t.id AS "TransactionId",
        t.data,
        pm."service" AS "PaymentService",
        spm."service" AS "SourcePaymentService",
        'Platform Tips'::TEXT AS "source",
        h.plan,
        CASE
          WHEN h."isActive" THEN h.id
          ELSE (h."settings"->'hostCollective'->>'id')::INT
        END as "chargedHostId"
      FROM
        "Transactions" t
      LEFT JOIN "Transactions" ot ON
        t."PlatformTipForTransactionGroup"::uuid = ot."TransactionGroup"
        AND ot.type = 'CREDIT'
        AND ot."PlatformTipForTransactionGroup" IS NULL
      LEFT JOIN "Collectives" h ON
        ot."HostCollectiveId" = h.id
      LEFT JOIN "Collectives" c ON
        ot."CollectiveId" = c.id
      LEFT JOIN "PaymentMethods" pm ON
        t."PaymentMethodId" = pm.id
      LEFT JOIN "PaymentMethods" spm ON
        spm.id = pm."SourcePaymentMethodId"
      WHERE
        t."createdAt" >= date_trunc('month', date :date - INTERVAL '1 month')
        AND t."createdAt" < date_trunc('month', date :date)
        AND t."deletedAt" IS NULL
        AND t."CollectiveId" = 8686
        AND t."PlatformTipForTransactionGroup" IS NOT NULL
        AND t."type" = 'CREDIT'
        AND ot."HostCollectiveId" NOT IN (8686)
        AND (
          pm."service" != 'stripe'
          OR pm.service IS NULL
        )
        AND (
          spm.service IS NULL
          OR spm.service != 'stripe'
        )
        AND (
          h."type" = 'ORGANIZATION'
          AND h."isHostAccount" = TRUE
        )
      ORDER BY
        t."createdAt"
    ),
    "platformFees" AS (
      SELECT
        t."createdAt",
        t.description,
        -t."platformFeeInHostCurrency" AS "amount",
        t."hostCurrency" AS "currency",
        t."CollectiveId",
        c."slug" AS "CollectiveSlug",
        t."HostCollectiveId",
        h."name" AS "HostName",
        t."OrderId",
        t.id AS "TransactionId",
        t.data,
        pm."service" AS "PaymentService",
        spm."service" AS "SourcePaymentService",
        'Platform Fees'::TEXT AS "source",
        h.plan,
        CASE
          WHEN h."isActive" THEN h.id
          ELSE (h."settings"->'hostCollective'->>'id')::INT
        END as "chargedHostId"
      FROM
        "Transactions" t
      LEFT JOIN "Collectives" h ON
        t."HostCollectiveId" = h.id
      LEFT JOIN "Collectives" c ON
        t."CollectiveId" = c.id
      LEFT JOIN "PaymentMethods" pm ON
        t."PaymentMethodId" = pm.id
      LEFT JOIN "PaymentMethods" spm ON
        spm.id = pm."SourcePaymentMethodId"
      WHERE
        t."createdAt" >= date_trunc('month', date :date - INTERVAL '1 month')
        AND t."createdAt" < date_trunc('month', date :date)
        AND t."deletedAt" IS NULL
        AND t."type" = 'CREDIT'
        AND t."platformFeeInHostCurrency" != 0
        AND t."HostCollectiveId" NOT IN (8686)
        AND (
          pm."service" != 'stripe'
          OR pm.service IS NULL
        )
        AND (
          spm.service IS NULL
          OR spm.service != 'stripe'
        )
        AND (
          h."type" = 'ORGANIZATION'
          AND h."isHostAccount" = TRUE
        )
      ORDER BY
        t."createdAt"
    ),
    "sharedRevenue" as (
      SELECT
        t."createdAt",
        t.description,
        -t."hostFeeInHostCurrency" AS "amount",
        t."hostCurrency" AS "currency",
        t."CollectiveId",
        c."slug" AS "CollectiveSlug",
        t."HostCollectiveId",
        h."name" AS "HostName",
        t."OrderId",
        t.id AS "TransactionId",
        t.data,
        pm."service" AS "PaymentService",
        spm."service" AS "SourcePaymentService",
        'Shared Revenue'::TEXT AS "source",
        h.plan,
        CASE
          WHEN h."isActive" THEN h.id
          ELSE (h."settings"->'hostCollective'->>'id')::INT
        END as "chargedHostId"
      FROM
        "Transactions" t
      LEFT JOIN "Collectives" h ON
        t."HostCollectiveId" = h.id
      LEFT JOIN "Collectives" c ON
        t."CollectiveId" = c.id
      LEFT JOIN "PaymentMethods" pm ON
        t."PaymentMethodId" = pm.id
      LEFT JOIN "PaymentMethods" spm ON
        spm.id = pm."SourcePaymentMethodId"
      WHERE
        t."createdAt" >= date_trunc('month', date :date - INTERVAL '1 month')
        AND t."createdAt" < date_trunc('month', date :date)
        AND t."deletedAt" IS NULL
        AND t."type" = 'CREDIT'
        AND t."hostFeeInHostCurrency" != 0
        -- Ignore transactions that incurred in platformFee
        AND t."platformFeeInHostCurrency" = 0
        AND t."data"->>'settled' IS NULL
        -- Ignore opensource and foundation:
        AND t."HostCollectiveId" NOT IN (11004, 11049, 8686)
      AND (
        h."type" = 'ORGANIZATION'
        AND h."isHostAccount" = TRUE
        AND h."plan" in ('${SHARED_REVENUE_PLANS.join("', '")}')
      )
    ORDER BY
      t."createdAt"
    ),
    "tipPaymentProcessorFee" AS (
      SELECT
        t."createdAt",
        t.description,
        round(t."paymentProcessorFeeInHostCurrency"::float / COALESCE((t."data"->>'hostToPlatformFxRate')::float, 1)) AS "amount",
        ot."hostCurrency" AS "currency",
        ot."CollectiveId",
        c."slug" AS "CollectiveSlug",
        ot."HostCollectiveId",
        h."name" AS "HostName",
        ot."OrderId",
        t.id AS "TransactionId",
        t.data,
        pm."service" AS "PaymentService",
        spm."service" AS "SourcePaymentService",
        'Reimburse: Payment Processor Fee for collected Platform Tips'::TEXT AS "source",
        h.plan,
        CASE
          WHEN h."isActive" THEN h.id
          ELSE (
            h."settings"->'hostCollective'->>'id'
          )::INT
        END AS "chargedHostId"
      FROM
        "Transactions" t
      LEFT JOIN "Transactions" ot ON
        t."PlatformTipForTransactionGroup"::uuid = ot."TransactionGroup"
        AND ot.type = 'CREDIT'
        AND ot."PlatformTipForTransactionGroup" IS NULL
      LEFT JOIN "Collectives" h ON
        ot."HostCollectiveId" = h.id
      LEFT JOIN "Collectives" c ON
        ot."CollectiveId" = c.id
      LEFT JOIN "PaymentMethods" pm ON
        t."PaymentMethodId" = pm.id
      LEFT JOIN "PaymentMethods" spm ON
        spm.id = pm."SourcePaymentMethodId"
      WHERE
        t."createdAt" >= date_trunc('month',  date :date - INTERVAL '1 month')
        AND t."createdAt" < date_trunc('month',  date :date)
        AND t."deletedAt" IS NULL
        AND t."CollectiveId" = 8686
        AND t."PlatformTipForTransactionGroup" IS NOT NULL
        AND t."type" = 'CREDIT'
        AND ot."HostCollectiveId" NOT IN (8686)
        AND (
          pm."service" = 'stripe'
          OR spm.service = 'stripe'
        )
        AND (
          h."type" = 'ORGANIZATION'
          AND h."isHostAccount" = TRUE
        )
      ORDER BY
        t."createdAt"
    )

    SELECT
      *
    FROM
      "platformFees"
    UNION
    SELECT
      *
    FROM
      "platformTips"
    UNION
    SELECT
      *
    FROM
      "sharedRevenue"
    UNION
    SELECT
      *
    FROM
      "tipPaymentProcessorFee";
  `,
    { replacements: { date: date.format('L') } },
  );
  const byHost = groupBy(pastMonthTransactions, 'HostCollectiveId');
  const today = moment.utc();
  const payoutMethods = groupBy(
    await models.PayoutMethod.findAll({
      where: { CollectiveId: SETTLEMENT_EXPENSE_PROPERTIES.FromCollectiveId },
    }),
    'type',
  );

  for (const [hostId, hostTransactions] of entries(byHost)) {
    if (HOST_ID && hostId != HOST_ID) {
      continue;
    }

    const { HostName, currency, plan: planId, chargedHostId } = hostTransactions[0];

    const hostFeeSharePercent = plans[planId]?.hostFeeSharePercent;
    const transactions = hostTransactions.map(t => {
      if (t.source === 'Shared Revenue') {
        t.amount = round(t.amount * (hostFeeSharePercent / 100));
      }
      return t;
    });

    let items = entries(groupBy(transactions, 'source')).map(([source, ts]) => {
      const incurredAt = date;
      const description = source;
      const amount = round(sumBy(ts, 'amount'));
      return { incurredAt, amount, description };
    });

    const host = await models.Collective.findByPk(hostId);
    const plan = await host.getPlan();
    if (plan.pricePerCollective) {
      const activeHostedCollectives = await host.getHostedCollectivesCount();
      const amount = (activeHostedCollectives || 0) * plan.pricePerCollective;
      if (amount) {
        items.push({
          incurredAt: new Date(),
          amount,
          description: 'Fixed Fee per Hosted Collective',
        });
      }
    }

    const transactionIds = transactions.map(t => t.TransactionId);
    const totalAmountCredited = sumBy(
      items
        .filter(i => i.description != 'Shared Revenue')
        .filter(i => i.description != 'Reimburse: Payment Processor Fee for collected Platform Tips')
        .filter(i => i.description != 'Fixed Fee per Hosted Collective'),
      'amount',
    );
    const totalAmountCharged = sumBy(items, 'amount');
    if (totalAmountCharged < 1000) {
      console.warn(
        `${HostName} (#${hostId}) skipped, total amound pending ${totalAmountCharged / 100} < 10.00 ${currency}.\n`,
      );
      continue;
    }
    console.info(
      `${HostName} (#${hostId}) has ${transactions.length} pending transactions and owes ${
        totalAmountCharged / 100
      } (${currency})`,
    );
    if (DRY) {
      console.debug(`Items:\n${json2csv(items)}\n`);
    }

    if (!DRY) {
      if (!chargedHostId) {
        console.error(`Warning: We don't have a way to submit the expense to ${HostName}, ignoring.\n`);
        continue;
      }
      if (totalAmountCharged > 0) {
        // Credit the Host with platform tips collected during the month
        await models.Transaction.create({
          amount: totalAmountCredited,
          amountInHostCurrency: totalAmountCredited,
          CollectiveId: chargedHostId,
          CreatedByUserId: SETTLEMENT_EXPENSE_PROPERTIES.UserId,
          currency: currency,
          description: `Platform Fees and Tips collected in ${moment.utc().subtract(1, 'month').format('MMMM')}`,
          FromCollectiveId: chargedHostId,
          HostCollectiveId: hostId,
          hostCurrency: currency,
          hostCurrencyFxRate: 1,
          netAmountInCollectiveCurrency: totalAmountCredited,
          type: TransactionTypes.CREDIT,
        });
      }

      const connectedAccounts = await host.getConnectedAccounts({
        where: { deletedAt: null },
      });

      let PayoutMethod =
        payoutMethods[PayoutMethodTypes.OTHER]?.[0] || payoutMethods[PayoutMethodTypes.BANK_ACCOUNT]?.[0];
      if (
        connectedAccounts?.find(c => c.service === 'transferwise') &&
        payoutMethods[PayoutMethodTypes.BANK_ACCOUNT]?.[0]
      ) {
        PayoutMethod = payoutMethods[PayoutMethodTypes.BANK_ACCOUNT]?.[0];
      } else if (
        connectedAccounts?.find(c => c.service === 'paypal') &&
        !host.settings?.disablePaypalPayouts &&
        payoutMethods[PayoutMethodTypes.PAYPAL]?.[0]
      ) {
        PayoutMethod = payoutMethods[PayoutMethodTypes.PAYPAL]?.[0];
      }

      // Create the Expense
      const expense = await models.Expense.create({
        ...SETTLEMENT_EXPENSE_PROPERTIES,
        PayoutMethodId: PayoutMethod.id,
        amount: totalAmountCharged,
        CollectiveId: chargedHostId,
        currency: currency,
        description: `Platform settlement for ${moment.utc().subtract(1, 'month').format('MMMM')}`,
        incurredAt: today,
        data: { isPlatformTipSettlement: true, transactionIds },
        type: expenseTypes.INVOICE,
        status: expenseStatus.PENDING,
      });

      // Create Expense Items
      items = items.map(i => ({
        ...i,
        ExpenseId: expense.id,
        CreatedByUserId: SETTLEMENT_EXPENSE_PROPERTIES.UserId,
      }));
      await models.ExpenseItem.bulkCreate(items);

      // Attach CSV
      const Body = json2csv(transactions.map(t => pick(t, ATTACHED_CSV_COLUMNS)));
      const filenameBase = `${HostName}-${moment(date).subtract(1, 'month').format('MMMM-YYYY')}`;
      const Key = `${filenameBase}.${generateKey().slice(0, 6)}.csv`;
      const { Location: url } = await uploadToS3({
        Bucket: config.aws.s3.bucket,
        Key,
        Body,
        ACL: 'public-read',
        ContentType: 'text/csv',
      });
      await models.ExpenseAttachedFile.create({
        url,
        ExpenseId: expense.id,
        CreatedByUserId: SETTLEMENT_EXPENSE_PROPERTIES.UserId,
      });
    }
  }
}

if (require.main === module) {
  run()
    .catch(e => {
      console.error(e);
      process.exit(1);
    })
    .then(() => {
      process.exit();
    });
}
<commit_msg>refact: invoice worker to use multiple currency settlement<commit_after>#!/usr/bin/env node
import '../../server/env';

import config from 'config';
import { parse as json2csv } from 'json2csv';
import { entries, groupBy, pick, round, sumBy } from 'lodash';
import moment from 'moment';

import expenseStatus from '../../server/constants/expense_status';
import expenseTypes from '../../server/constants/expense_type';
import plans, { SHARED_REVENUE_PLANS } from '../../server/constants/plans';
import { SETTLEMENT_EXPENSE_PROPERTIES, TransactionTypes } from '../../server/constants/transactions';
import { uploadToS3 } from '../../server/lib/awsS3';
import { generateKey } from '../../server/lib/encryption';
import models, { sequelize } from '../../server/models';
import { PayoutMethodTypes } from '../../server/models/PayoutMethod';

const date = process.env.START_DATE ? moment.utc(process.env.START_DATE) : moment.utc();
const DRY = process.env.DRY;
const HOST_ID = process.env.HOST_ID;
const isProduction = config.env === 'production';

// Only run on the 1th of the month
if (isProduction && date.date() !== 1 && !process.env.OFFCYCLE) {
  console.log('OC_ENV is production and today is not the 1st of month, script aborted!');
  process.exit();
}

if (DRY) {
  console.info('Running dry, changes are not going to be persisted to the DB.');
}

const ATTACHED_CSV_COLUMNS = [
  'createdAt',
  'description',
  'CollectiveSlug',
  'amount',
  'currency',
  'OrderId',
  'TransactionId',
  'PaymentService',
  'source',
];

export async function run() {
  console.info(`Invoicing hosts pending fees and tips for ${moment(date).subtract(1, 'month').format('MMMM')}.`);
  const [pastMonthTransactions] = await sequelize.query(
    `
    WITH "platformTips" AS (
      SELECT
        t."createdAt",
        t.description,
        round(t."netAmountInCollectiveCurrency"::float / COALESCE((t."data"->>'hostToPlatformFxRate')::float, 1)) AS "amount",
        ot."hostCurrency" AS "currency",
        ot."CollectiveId",
        c."slug" AS "CollectiveSlug",
        ot."HostCollectiveId",
        h."name" AS "HostName",
        ot."OrderId",
        t.id AS "TransactionId",
        t.data,
        pm."service" AS "PaymentService",
        spm."service" AS "SourcePaymentService",
        'Platform Tips'::TEXT AS "source",
        h.plan,
        CASE
          WHEN h."isActive" THEN h.id
          ELSE (h."settings"->'hostCollective'->>'id')::INT
        END as "chargedHostId"
      FROM
        "Transactions" t
      LEFT JOIN "Transactions" ot ON
        t."PlatformTipForTransactionGroup"::uuid = ot."TransactionGroup"
        AND ot.type = 'CREDIT'
        AND ot."PlatformTipForTransactionGroup" IS NULL
      LEFT JOIN "Collectives" h ON
        ot."HostCollectiveId" = h.id
      LEFT JOIN "Collectives" c ON
        ot."CollectiveId" = c.id
      LEFT JOIN "PaymentMethods" pm ON
        t."PaymentMethodId" = pm.id
      LEFT JOIN "PaymentMethods" spm ON
        spm.id = pm."SourcePaymentMethodId"
      WHERE
        t."createdAt" >= date_trunc('month', date :date - INTERVAL '1 month')
        AND t."createdAt" < date_trunc('month', date :date)
        AND t."deletedAt" IS NULL
        AND t."CollectiveId" = 8686
        AND t."PlatformTipForTransactionGroup" IS NOT NULL
        AND t."type" = 'CREDIT'
        AND ot."HostCollectiveId" NOT IN (8686)
        AND (
          pm."service" != 'stripe'
          OR pm.service IS NULL
        )
        AND (
          spm.service IS NULL
          OR spm.service != 'stripe'
        )
        AND (
          h."type" = 'ORGANIZATION'
          AND h."isHostAccount" = TRUE
        )
      ORDER BY
        t."createdAt"
    ),
    "platformFees" AS (
      SELECT
        t."createdAt",
        t.description,
        -t."platformFeeInHostCurrency" AS "amount",
        t."hostCurrency" AS "currency",
        t."CollectiveId",
        c."slug" AS "CollectiveSlug",
        t."HostCollectiveId",
        h."name" AS "HostName",
        t."OrderId",
        t.id AS "TransactionId",
        t.data,
        pm."service" AS "PaymentService",
        spm."service" AS "SourcePaymentService",
        'Platform Fees'::TEXT AS "source",
        h.plan,
        CASE
          WHEN h."isActive" THEN h.id
          ELSE (h."settings"->'hostCollective'->>'id')::INT
        END as "chargedHostId"
      FROM
        "Transactions" t
      LEFT JOIN "Collectives" h ON
        t."HostCollectiveId" = h.id
      LEFT JOIN "Collectives" c ON
        t."CollectiveId" = c.id
      LEFT JOIN "PaymentMethods" pm ON
        t."PaymentMethodId" = pm.id
      LEFT JOIN "PaymentMethods" spm ON
        spm.id = pm."SourcePaymentMethodId"
      WHERE
        t."createdAt" >= date_trunc('month', date :date - INTERVAL '1 month')
        AND t."createdAt" < date_trunc('month', date :date)
        AND t."deletedAt" IS NULL
        AND t."type" = 'CREDIT'
        AND t."platformFeeInHostCurrency" != 0
        AND t."HostCollectiveId" NOT IN (8686)
        AND (
          pm."service" != 'stripe'
          OR pm.service IS NULL
        )
        AND (
          spm.service IS NULL
          OR spm.service != 'stripe'
        )
        AND (
          h."type" = 'ORGANIZATION'
          AND h."isHostAccount" = TRUE
        )
      ORDER BY
        t."createdAt"
    ),
    "sharedRevenue" as (
      SELECT
        t."createdAt",
        t.description,
        -t."hostFeeInHostCurrency" AS "amount",
        t."hostCurrency" AS "currency",
        t."CollectiveId",
        c."slug" AS "CollectiveSlug",
        t."HostCollectiveId",
        h."name" AS "HostName",
        t."OrderId",
        t.id AS "TransactionId",
        t.data,
        pm."service" AS "PaymentService",
        spm."service" AS "SourcePaymentService",
        'Shared Revenue'::TEXT AS "source",
        h.plan,
        CASE
          WHEN h."isActive" THEN h.id
          ELSE (h."settings"->'hostCollective'->>'id')::INT
        END as "chargedHostId"
      FROM
        "Transactions" t
      LEFT JOIN "Collectives" h ON
        t."HostCollectiveId" = h.id
      LEFT JOIN "Collectives" c ON
        t."CollectiveId" = c.id
      LEFT JOIN "PaymentMethods" pm ON
        t."PaymentMethodId" = pm.id
      LEFT JOIN "PaymentMethods" spm ON
        spm.id = pm."SourcePaymentMethodId"
      WHERE
        t."createdAt" >= date_trunc('month', date :date - INTERVAL '1 month')
        AND t."createdAt" < date_trunc('month', date :date)
        AND t."deletedAt" IS NULL
        AND t."type" = 'CREDIT'
        AND t."hostFeeInHostCurrency" != 0
        -- Ignore transactions that incurred in platformFee
        AND t."platformFeeInHostCurrency" = 0
        AND t."data"->>'settled' IS NULL
        -- Ignore opensource and foundation:
        AND t."HostCollectiveId" NOT IN (11004, 11049, 8686)
      AND (
        h."type" = 'ORGANIZATION'
        AND h."isHostAccount" = TRUE
        AND h."plan" in ('${SHARED_REVENUE_PLANS.join("', '")}')
      )
    ORDER BY
      t."createdAt"
    ),
    "tipPaymentProcessorFee" AS (
      SELECT
        t."createdAt",
        t.description,
        round(t."paymentProcessorFeeInHostCurrency"::float / COALESCE((t."data"->>'hostToPlatformFxRate')::float, 1)) AS "amount",
        ot."hostCurrency" AS "currency",
        ot."CollectiveId",
        c."slug" AS "CollectiveSlug",
        ot."HostCollectiveId",
        h."name" AS "HostName",
        ot."OrderId",
        t.id AS "TransactionId",
        t.data,
        pm."service" AS "PaymentService",
        spm."service" AS "SourcePaymentService",
        'Reimburse: Payment Processor Fee for collected Platform Tips'::TEXT AS "source",
        h.plan,
        CASE
          WHEN h."isActive" THEN h.id
          ELSE (
            h."settings"->'hostCollective'->>'id'
          )::INT
        END AS "chargedHostId"
      FROM
        "Transactions" t
      LEFT JOIN "Transactions" ot ON
        t."PlatformTipForTransactionGroup"::uuid = ot."TransactionGroup"
        AND ot.type = 'CREDIT'
        AND ot."PlatformTipForTransactionGroup" IS NULL
      LEFT JOIN "Collectives" h ON
        ot."HostCollectiveId" = h.id
      LEFT JOIN "Collectives" c ON
        ot."CollectiveId" = c.id
      LEFT JOIN "PaymentMethods" pm ON
        t."PaymentMethodId" = pm.id
      LEFT JOIN "PaymentMethods" spm ON
        spm.id = pm."SourcePaymentMethodId"
      WHERE
        t."createdAt" >= date_trunc('month',  date :date - INTERVAL '1 month')
        AND t."createdAt" < date_trunc('month',  date :date)
        AND t."deletedAt" IS NULL
        AND t."CollectiveId" = 8686
        AND t."PlatformTipForTransactionGroup" IS NOT NULL
        AND t."type" = 'CREDIT'
        AND ot."HostCollectiveId" NOT IN (8686)
        AND (
          pm."service" = 'stripe'
          OR spm.service = 'stripe'
        )
        AND (
          h."type" = 'ORGANIZATION'
          AND h."isHostAccount" = TRUE
        )
      ORDER BY
        t."createdAt"
    )

    SELECT
      *
    FROM
      "platformFees"
    UNION
    SELECT
      *
    FROM
      "platformTips"
    UNION
    SELECT
      *
    FROM
      "sharedRevenue"
    UNION
    SELECT
      *
    FROM
      "tipPaymentProcessorFee";
  `,
    { replacements: { date: date.format('L') } },
  );
  const byHost = groupBy(pastMonthTransactions, 'HostCollectiveId');
  const today = moment.utc();
  const payoutMethods = groupBy(
    await models.PayoutMethod.findAll({
      where: { CollectiveId: SETTLEMENT_EXPENSE_PROPERTIES.FromCollectiveId },
    }),
    'type',
  );

  for (const [hostId, hostTransactions] of entries(byHost)) {
    if (HOST_ID && hostId != HOST_ID) {
      continue;
    }

    const { HostName, currency, plan: planId, chargedHostId } = hostTransactions[0];

    const hostFeeSharePercent = plans[planId]?.hostFeeSharePercent;
    const transactions = hostTransactions.map(t => {
      if (t.source === 'Shared Revenue') {
        t.amount = round(t.amount * (hostFeeSharePercent / 100));
      }
      return t;
    });

    let items = entries(groupBy(transactions, 'source')).map(([source, ts]) => {
      const incurredAt = date;
      const description = source;
      const amount = round(sumBy(ts, 'amount'));
      return { incurredAt, amount, description };
    });

    const host = await models.Collective.findByPk(hostId);
    const plan = await host.getPlan();
    if (plan.pricePerCollective) {
      const activeHostedCollectives = await host.getHostedCollectivesCount();
      const amount = (activeHostedCollectives || 0) * plan.pricePerCollective;
      if (amount) {
        items.push({
          incurredAt: new Date(),
          amount,
          description: 'Fixed Fee per Hosted Collective',
        });
      }
    }

    const transactionIds = transactions.map(t => t.TransactionId);
    const totalAmountCredited = sumBy(
      items
        .filter(i => i.description != 'Shared Revenue')
        .filter(i => i.description != 'Reimburse: Payment Processor Fee for collected Platform Tips')
        .filter(i => i.description != 'Fixed Fee per Hosted Collective'),
      'amount',
    );
    const totalAmountCharged = sumBy(items, 'amount');
    if (totalAmountCharged < 1000) {
      console.warn(
        `${HostName} (#${hostId}) skipped, total amound pending ${totalAmountCharged / 100} < 10.00 ${currency}.\n`,
      );
      continue;
    }
    console.info(
      `${HostName} (#${hostId}) has ${transactions.length} pending transactions and owes ${
        totalAmountCharged / 100
      } (${currency})`,
    );
    if (DRY) {
      console.debug(`Items:\n${json2csv(items)}\n`);
    }

    if (!DRY) {
      if (!chargedHostId) {
        console.error(`Warning: We don't have a way to submit the expense to ${HostName}, ignoring.\n`);
        continue;
      }
      if (totalAmountCharged > 0) {
        // Credit the Host with platform tips collected during the month
        await models.Transaction.create({
          amount: totalAmountCredited,
          amountInHostCurrency: totalAmountCredited,
          CollectiveId: chargedHostId,
          CreatedByUserId: SETTLEMENT_EXPENSE_PROPERTIES.UserId,
          currency: currency,
          description: `Platform Fees and Tips collected in ${moment.utc().subtract(1, 'month').format('MMMM')}`,
          FromCollectiveId: chargedHostId,
          HostCollectiveId: hostId,
          hostCurrency: currency,
          hostCurrencyFxRate: 1,
          netAmountInCollectiveCurrency: totalAmountCredited,
          type: TransactionTypes.CREDIT,
        });
      }

      const connectedAccounts = await host.getConnectedAccounts({
        where: { deletedAt: null },
      });

      let PayoutMethod =
        payoutMethods[PayoutMethodTypes.OTHER]?.[0] || payoutMethods[PayoutMethodTypes.BANK_ACCOUNT]?.[0];
      if (
        connectedAccounts?.find(c => c.service === 'transferwise') &&
        payoutMethods[PayoutMethodTypes.BANK_ACCOUNT]?.[0]
      ) {
        const currencyCompatibleAccount = payoutMethods[PayoutMethodTypes.BANK_ACCOUNT].find(
          pm => pm.data?.currency === currency,
        );
        PayoutMethod = currencyCompatibleAccount || payoutMethods[PayoutMethodTypes.BANK_ACCOUNT]?.[0];
      } else if (
        connectedAccounts?.find(c => c.service === 'paypal') &&
        !host.settings?.disablePaypalPayouts &&
        payoutMethods[PayoutMethodTypes.PAYPAL]?.[0]
      ) {
        PayoutMethod = payoutMethods[PayoutMethodTypes.PAYPAL]?.[0];
      }

      // Create the Expense
      const expense = await models.Expense.create({
        ...SETTLEMENT_EXPENSE_PROPERTIES,
        PayoutMethodId: PayoutMethod.id,
        amount: totalAmountCharged,
        CollectiveId: chargedHostId,
        currency: currency,
        description: `Platform settlement for ${moment.utc().subtract(1, 'month').format('MMMM')}`,
        incurredAt: today,
        data: { isPlatformTipSettlement: true, transactionIds },
        type: expenseTypes.INVOICE,
        status: expenseStatus.PENDING,
      });

      // Create Expense Items
      items = items.map(i => ({
        ...i,
        ExpenseId: expense.id,
        CreatedByUserId: SETTLEMENT_EXPENSE_PROPERTIES.UserId,
      }));
      await models.ExpenseItem.bulkCreate(items);

      // Attach CSV
      const Body = json2csv(transactions.map(t => pick(t, ATTACHED_CSV_COLUMNS)));
      const filenameBase = `${HostName}-${moment(date).subtract(1, 'month').format('MMMM-YYYY')}`;
      const Key = `${filenameBase}.${generateKey().slice(0, 6)}.csv`;
      const { Location: url } = await uploadToS3({
        Bucket: config.aws.s3.bucket,
        Key,
        Body,
        ACL: 'public-read',
        ContentType: 'text/csv',
      });
      await models.ExpenseAttachedFile.create({
        url,
        ExpenseId: expense.id,
        CreatedByUserId: SETTLEMENT_EXPENSE_PROPERTIES.UserId,
      });
    }
  }
}

if (require.main === module) {
  run()
    .catch(e => {
      console.error(e);
      process.exit(1);
    })
    .then(() => {
      process.exit();
    });
}
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2012 Adobe Systems Incorporated. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, forin: true, maxerr: 50, regexp: true */
/*global define, $, less, window */

/**
 * main integrates LiveDevelopment into Brackets
 *
 * This module creates two menu items:
 *
 *  "Go Live": open or close a Live Development session and visualize the status
 *  "Highlight": toggle source highlighting
 */
define(function main(require, exports, module) {
    "use strict";

    var DocumentManager     = require("document/DocumentManager"),
        Commands            = require("command/Commands"),
        AppInit             = require("utils/AppInit"),
        LiveDevelopment     = require("LiveDevelopment/LiveDevelopment"),
        Inspector           = require("LiveDevelopment/Inspector/Inspector"),
        CommandManager      = require("command/CommandManager"),
        PreferencesManager  = require("preferences/PreferencesManager"),
        Dialogs             = require("widgets/Dialogs"),
        DefaultDialogs      = require("widgets/DefaultDialogs"),
        UrlParams           = require("utils/UrlParams").UrlParams,
        Strings             = require("strings"),
        ExtensionUtils      = require("utils/ExtensionUtils"),
        StringUtils         = require("utils/StringUtils");

    // expermiental multi-browser implementation
    var MultiBrowserLiveDev = require("LiveDevelopment/LiveDevMultiBrowser");

    var params = new UrlParams();
    var config = {
        experimental: false, // enable experimental features
        debug: true, // enable debug output and helpers
        autoconnect: false, // go live automatically after startup?
        highlight: true, // enable highlighting?
        highlightConfig: { // the highlight configuration for the Inspector
            borderColor:  {r: 255, g: 229, b: 153, a: 0.66},
            contentColor: {r: 111, g: 168, b: 220, a: 0.55},
            marginColor:  {r: 246, g: 178, b: 107, a: 0.66},
            paddingColor: {r: 147, g: 196, b: 125, a: 0.66},
            showInfo: true
        }
    };
    // Status labels/styles are ordered: error, not connected, progress1, progress2, connected.
    var _statusTooltip,
        _statusStyle,
        _allStatusStyles;

    var _$btnGoLive; // reference to the GoLive button

    // current selected implementation (LiveDevelopment | LiveDevMultiBrowser)
    var LiveDevImpl;

    /** Load Live Development LESS Style */
    function _loadStyles() {
        var lessText    = require("text!LiveDevelopment/main.less"),
            parser      = new less.Parser();

        parser.parse(lessText, function onParse(err, tree) {
            console.assert(!err, err);
            ExtensionUtils.addEmbeddedStyleSheet(tree.toCSS());
        });
    }

    /**
     * Change the appearance of a button. Omit text to remove any extra text; omit style to return to default styling;
     * omit tooltip to leave tooltip unchanged.
     */
    function _setLabel($btn, text, style, tooltip) {
        // Clear text/styles from previous status
        $("span", $btn).remove();
        $btn.removeClass(_allStatusStyles);

        // Set text/styles for new status
        if (text && text.length > 0) {
            $("")
                .addClass(style)
                .text(text)
                .appendTo($btn);
        } else {
            $btn.addClass(style);
        }

        if (tooltip) {
            $btn.attr("title", tooltip);
        }
    }

    /**
     * Toggles LiveDevelopment and synchronizes the state of UI elements that reports LiveDevelopment status
     *
     * Stop Live Dev when in an active state (ACTIVE, OUT_OF_SYNC, SYNC_ERROR).
     * Start Live Dev when in an inactive state (ERROR, INACTIVE).
     * Do nothing when in a connecting state (CONNECTING, LOADING_AGENTS).
     */
    function _handleGoLiveCommand() {
        if (LiveDevImpl.status >= LiveDevImpl.STATUS_ACTIVE) {
            LiveDevImpl.close();
        } else if (LiveDevImpl.status <= LiveDevImpl.STATUS_INACTIVE) {
            if (!params.get("skipLiveDevelopmentInfo") && !PreferencesManager.getViewState("livedev.afterFirstLaunch")) {
                PreferencesManager.setViewState("livedev.afterFirstLaunch", "true");
                Dialogs.showModalDialog(
                    DefaultDialogs.DIALOG_ID_INFO,
                    Strings.LIVE_DEVELOPMENT_INFO_TITLE,
                    Strings.LIVE_DEVELOPMENT_INFO_MESSAGE
                ).done(function (id) {
                    LiveDevImpl.open();
                });
            } else {
                LiveDevImpl.open();
            }
        }
    }

    /** Called on status change */
    function _showStatusChangeReason(reason) {
        // Destroy the previous twipsy (options are not updated otherwise)
        _$btnGoLive.twipsy("hide").removeData("twipsy");

        // If there was no reason or the action was an explicit request by the user, don't show a twipsy
        if (!reason || reason === "explicit_close") {
            return;
        }

        // Translate the reason
        var translatedReason = Strings["LIVE_DEV_" + reason.toUpperCase()];
        if (!translatedReason) {
            translatedReason = StringUtils.format(Strings.LIVE_DEV_CLOSED_UNKNOWN_REASON, reason);
        }

        // Configure the twipsy
        var options = {
            placement: "left",
            trigger: "manual",
            autoHideDelay: 5000,
            title: function () {
                return translatedReason;
            }
        };

        // Show the twipsy with the explanation
        _$btnGoLive.twipsy(options).twipsy("show");
    }

    /** Create the menu item "Go Live" */
    function _setupGoLiveButton() {
        if (!_$btnGoLive) {
            _$btnGoLive = $("#toolbar-go-live");
            _$btnGoLive.click(function onGoLive() {
                _handleGoLiveCommand();
            });
        }
        LiveDevImpl.on("statusChange", function statusChange(event, status, reason) {
            // status starts at -1 (error), so add one when looking up name and style
            // See the comments at the top of LiveDevelopment.js for details on the
            // various status codes.
            _setLabel(_$btnGoLive, null, _statusStyle[status + 1], _statusTooltip[status + 1]);
            _showStatusChangeReason(reason);
            if (config.autoconnect) {
                window.sessionStorage.setItem("live.enabled", status === 3);
            }
        });

        // Initialize tooltip for 'not connected' state
        _setLabel(_$btnGoLive, null, _statusStyle[1], _statusTooltip[1]);
    }

    /** Maintains state of the Live Preview menu item */
    function _setupGoLiveMenu() {
        LiveDevImpl.on("statusChange", function statusChange(event, status) {
            // Update the checkmark next to 'Live Preview' menu item
            // Add checkmark when status is STATUS_ACTIVE; otherwise remove it
            CommandManager.get(Commands.FILE_LIVE_FILE_PREVIEW).setChecked(status === LiveDevImpl.STATUS_ACTIVE);
            CommandManager.get(Commands.FILE_LIVE_HIGHLIGHT).setEnabled(status === LiveDevImpl.STATUS_ACTIVE);
        });
    }

    function _updateHighlightCheckmark() {
        CommandManager.get(Commands.FILE_LIVE_HIGHLIGHT).setChecked(config.highlight);
    }

    function _handlePreviewHighlightCommand() {
        config.highlight = !config.highlight;
        _updateHighlightCheckmark();
        if (config.highlight) {
            LiveDevImpl.showHighlight();
        } else {
            LiveDevImpl.hideHighlight();
        }
        PreferencesManager.setViewState("livedev.highlight", config.highlight);
    }

    // sets the MultiBrowserLiveDev implementation if multibrowser = true,
    // keeps default LiveDevelopment implementation based on CDT in other case.
    // since UI status are slightly different btw implementations, it also set
    // the corresponding style values.
    function _setImplementation(multibrowser) {
        console.log('set implementation ' + multibrowser);
        if (multibrowser) {
            // set implemenation
            LiveDevImpl = MultiBrowserLiveDev;
            // update styles for UI status
            _statusTooltip = [
                Strings.LIVE_DEV_STATUS_TIP_NOT_CONNECTED,
                Strings.LIVE_DEV_STATUS_TIP_NOT_CONNECTED,
                Strings.LIVE_DEV_STATUS_TIP_PROGRESS1,
                Strings.LIVE_DEV_STATUS_TIP_CONNECTED,
                Strings.LIVE_DEV_STATUS_TIP_OUT_OF_SYNC,
                Strings.LIVE_DEV_STATUS_TIP_SYNC_ERROR,
                Strings.LIVE_DEV_STATUS_TIP_PROGRESS1,
                Strings.LIVE_DEV_STATUS_TIP_PROGRESS1
            ];
            _statusStyle = ["warning", "", "info", "success", "out-of-sync", "sync-error", "info", "info"];  // Status indicator's CSS class
        } else {
            LiveDevImpl = LiveDevelopment;
            _statusTooltip = [
                Strings.LIVE_DEV_STATUS_TIP_NOT_CONNECTED,
                Strings.LIVE_DEV_STATUS_TIP_NOT_CONNECTED,
                Strings.LIVE_DEV_STATUS_TIP_PROGRESS1,
                Strings.LIVE_DEV_STATUS_TIP_PROGRESS2,
                Strings.LIVE_DEV_STATUS_TIP_CONNECTED,
                Strings.LIVE_DEV_STATUS_TIP_OUT_OF_SYNC,
                Strings.LIVE_DEV_STATUS_TIP_SYNC_ERROR
            ];
            _statusStyle = ["warning", "", "info", "info", "success", "out-of-sync", "sync-error"];  // Status indicator's CSS class
        }
        _allStatusStyles = _statusStyle.join(" ");
    }

    /** Setup window references to useful LiveDevelopment modules */
    function _setupDebugHelpers() {
        window.ld = LiveDevelopment;
        window.i = Inspector;
        window.report = function report(params) { window.params = params; console.info(params); };
    }

    /** force reload the live preview */
    function _handleReloadLivePreviewCommand() {
        if (LiveDevelopment.status >= LiveDevelopment.STATUS_ACTIVE) {
            LiveDevelopment.reload();
        }
    }

    /** Initialize LiveDevelopment */
    AppInit.appReady(function () {
        params.parse();

        Inspector.init(config);
        LiveDevelopment.init(config);

        // init experimental multi-browser implementation
        // it can be enable by setting 'livedev.multibrowser' preference to true.
        // It has to be initiated at this point in case of dynamically switching
        // by changing the preference value.
        MultiBrowserLiveDev.init(config);

        _loadStyles();
        _setupGoLiveButton();
        _setupGoLiveMenu();

        _updateHighlightCheckmark();

        if (config.debug) {
            _setupDebugHelpers();
        }

        // trigger autoconnect
        if (config.autoconnect &&
                window.sessionStorage.getItem("live.enabled") === "true" &&
                DocumentManager.getCurrentDocument()) {
            _handleGoLiveCommand();
        }

        // Redraw highlights when window gets focus. This ensures that the highlights
        // will be in sync with any DOM changes that may have occurred.
        $(window).focus(function () {
            if (Inspector.connected() && config.highlight) {
                LiveDevelopment.redrawHighlight();
            }
        });
    });

    // init prefs
    PreferencesManager.stateManager.definePreference("livedev.highlight", "boolean", true)
        .on("change", function () {
            config.highlight = PreferencesManager.getViewState("livedev.highlight");
            _updateHighlightCheckmark();
        });

    PreferencesManager.convertPreferences(module, {
        "highlight": "user livedev.highlight",
        "afterFirstLaunch": "user livedev.afterFirstLaunch"
    }, true);

    PreferencesManager.definePreference("livedev.multibrowser", "boolean", false)
        .on("change", function () {
            // stop the current session if it is open and set implementation based on
            // the pref value. It could be automaticallty restarted but, since the preference file,
            // is the document open in the editor, it will no launch a live document.
            if (LiveDevImpl && LiveDevImpl.status >= LiveDevImpl.STATUS_ACTIVE) {
                LiveDevImpl.close();
                // status changes will now be listen from the new implementation
                LiveDevImpl.off('statusChange');
            }
            _setImplementation(PreferencesManager.get('livedev.multibrowser'));
            // setup status changes listeners for new implementation
            _setupGoLiveButton();
            _setupGoLiveMenu();
        });

    config.highlight = PreferencesManager.getViewState("livedev.highlight");

    // init commands
    CommandManager.register(Strings.CMD_LIVE_FILE_PREVIEW,  Commands.FILE_LIVE_FILE_PREVIEW, _handleGoLiveCommand);
    CommandManager.register(Strings.CMD_LIVE_HIGHLIGHT, Commands.FILE_LIVE_HIGHLIGHT, _handlePreviewHighlightCommand);
    CommandManager.register(Strings.CMD_RELOAD_LIVE_PREVIEW, Commands.CMD_RELOAD_LIVE_PREVIEW, _handleReloadLivePreviewCommand);
    CommandManager.get(Commands.FILE_LIVE_HIGHLIGHT).setEnabled(false);

    // Export public functions
});
<commit_msg>convert status styles/tooltips array to object<commit_after>/*
 * Copyright (c) 2012 Adobe Systems Incorporated. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, forin: true, maxerr: 50, regexp: true */
/*global define, $, less, window */

/**
 * main integrates LiveDevelopment into Brackets
 *
 * This module creates two menu items:
 *
 *  "Go Live": open or close a Live Development session and visualize the status
 *  "Highlight": toggle source highlighting
 */
define(function main(require, exports, module) {
    "use strict";

    var DocumentManager     = require("document/DocumentManager"),
        Commands            = require("command/Commands"),
        AppInit             = require("utils/AppInit"),
        LiveDevelopment     = require("LiveDevelopment/LiveDevelopment"),
        Inspector           = require("LiveDevelopment/Inspector/Inspector"),
        CommandManager      = require("command/CommandManager"),
        PreferencesManager  = require("preferences/PreferencesManager"),
        Dialogs             = require("widgets/Dialogs"),
        DefaultDialogs      = require("widgets/DefaultDialogs"),
        UrlParams           = require("utils/UrlParams").UrlParams,
        Strings             = require("strings"),
        ExtensionUtils      = require("utils/ExtensionUtils"),
        StringUtils         = require("utils/StringUtils");

    // expermiental multi-browser implementation
    var MultiBrowserLiveDev = require("LiveDevelopment/LiveDevMultiBrowser");

    var params = new UrlParams();
    var config = {
        experimental: false, // enable experimental features
        debug: true, // enable debug output and helpers
        autoconnect: false, // go live automatically after startup?
        highlight: true, // enable highlighting?
        highlightConfig: { // the highlight configuration for the Inspector
            borderColor:  {r: 255, g: 229, b: 153, a: 0.66},
            contentColor: {r: 111, g: 168, b: 220, a: 0.55},
            marginColor:  {r: 246, g: 178, b: 107, a: 0.66},
            paddingColor: {r: 147, g: 196, b: 125, a: 0.66},
            showInfo: true
        }
    };
    // Status labels/styles are ordered: error, not connected, progress1, progress2, connected.
    var _status,
        _allStatusStyles = ["warning", "info", "success", "out-of-sync", "sync-error"].join(" ");

    var _$btnGoLive; // reference to the GoLive button

    // current selected implementation (LiveDevelopment | LiveDevMultiBrowser)
    var LiveDevImpl;

    /** Load Live Development LESS Style */
    function _loadStyles() {
        var lessText    = require("text!LiveDevelopment/main.less"),
            parser      = new less.Parser();

        parser.parse(lessText, function onParse(err, tree) {
            console.assert(!err, err);
            ExtensionUtils.addEmbeddedStyleSheet(tree.toCSS());
        });
    }

    /**
     * Change the appearance of a button. Omit text to remove any extra text; omit style to return to default styling;
     * omit tooltip to leave tooltip unchanged.
     */
    function _setLabel($btn, text, style, tooltip) {
        // Clear text/styles from previous status
        $("span", $btn).remove();
        $btn.removeClass(_allStatusStyles);

        // Set text/styles for new status
        if (text && text.length > 0) {
            $("")
                .addClass(style)
                .text(text)
                .appendTo($btn);
        } else {
            $btn.addClass(style);
        }

        if (tooltip) {
            $btn.attr("title", tooltip);
        }
    }

    /**
     * Toggles LiveDevelopment and synchronizes the state of UI elements that reports LiveDevelopment status
     *
     * Stop Live Dev when in an active state (ACTIVE, OUT_OF_SYNC, SYNC_ERROR).
     * Start Live Dev when in an inactive state (ERROR, INACTIVE).
     * Do nothing when in a connecting state (CONNECTING, LOADING_AGENTS).
     */
    function _handleGoLiveCommand() {
        if (LiveDevImpl.status >= LiveDevImpl.STATUS_ACTIVE) {
            LiveDevImpl.close();
        } else if (LiveDevImpl.status <= LiveDevImpl.STATUS_INACTIVE) {
            if (!params.get("skipLiveDevelopmentInfo") && !PreferencesManager.getViewState("livedev.afterFirstLaunch")) {
                PreferencesManager.setViewState("livedev.afterFirstLaunch", "true");
                Dialogs.showModalDialog(
                    DefaultDialogs.DIALOG_ID_INFO,
                    Strings.LIVE_DEVELOPMENT_INFO_TITLE,
                    Strings.LIVE_DEVELOPMENT_INFO_MESSAGE
                ).done(function (id) {
                    LiveDevImpl.open();
                });
            } else {
                LiveDevImpl.open();
            }
        }
    }

    /** Called on status change */
    function _showStatusChangeReason(reason) {
        // Destroy the previous twipsy (options are not updated otherwise)
        _$btnGoLive.twipsy("hide").removeData("twipsy");

        // If there was no reason or the action was an explicit request by the user, don't show a twipsy
        if (!reason || reason === "explicit_close") {
            return;
        }

        // Translate the reason
        var translatedReason = Strings["LIVE_DEV_" + reason.toUpperCase()];
        if (!translatedReason) {
            translatedReason = StringUtils.format(Strings.LIVE_DEV_CLOSED_UNKNOWN_REASON, reason);
        }

        // Configure the twipsy
        var options = {
            placement: "left",
            trigger: "manual",
            autoHideDelay: 5000,
            title: function () {
                return translatedReason;
            }
        };

        // Show the twipsy with the explanation
        _$btnGoLive.twipsy(options).twipsy("show");
    }

    /** Create the menu item "Go Live" */
    function _setupGoLiveButton() {
        if (!_$btnGoLive) {
            _$btnGoLive = $("#toolbar-go-live");
            _$btnGoLive.click(function onGoLive() {
                _handleGoLiveCommand();
            });
        }
        LiveDevImpl.on("statusChange", function statusChange(event, status, reason) {
            // status starts at -1 (error), so add one when looking up name and style
            // See the comments at the top of LiveDevelopment.js for details on the
            // various status codes.
            _setLabel(_$btnGoLive, null, _status[status + 1].style, _status[status + 1].tooltip);
            _showStatusChangeReason(reason);
            if (config.autoconnect) {
                window.sessionStorage.setItem("live.enabled", status === 3);
            }
        });

        // Initialize tooltip for 'not connected' state
        _setLabel(_$btnGoLive, null, _status[1].style, _status[1].tooltip);
    }

    /** Maintains state of the Live Preview menu item */
    function _setupGoLiveMenu() {
        LiveDevImpl.on("statusChange", function statusChange(event, status) {
            // Update the checkmark next to 'Live Preview' menu item
            // Add checkmark when status is STATUS_ACTIVE; otherwise remove it
            CommandManager.get(Commands.FILE_LIVE_FILE_PREVIEW).setChecked(status === LiveDevImpl.STATUS_ACTIVE);
            CommandManager.get(Commands.FILE_LIVE_HIGHLIGHT).setEnabled(status === LiveDevImpl.STATUS_ACTIVE);
        });
    }

    function _updateHighlightCheckmark() {
        CommandManager.get(Commands.FILE_LIVE_HIGHLIGHT).setChecked(config.highlight);
    }

    function _handlePreviewHighlightCommand() {
        config.highlight = !config.highlight;
        _updateHighlightCheckmark();
        if (config.highlight) {
            LiveDevImpl.showHighlight();
        } else {
            LiveDevImpl.hideHighlight();
        }
        PreferencesManager.setViewState("livedev.highlight", config.highlight);
    }

    // sets the MultiBrowserLiveDev implementation if multibrowser = true,
    // keeps default LiveDevelopment implementation based on CDT in other case.
    // since UI status are slightly different btw implementations, it also set
    // the corresponding style values.
    function _setImplementation(multibrowser) {
        console.log('set implementation ' + multibrowser);
        if (multibrowser) {
            // set implemenation
            LiveDevImpl = MultiBrowserLiveDev;
            // update styles for UI status
            _status = [
                { tooltip: Strings.LIVE_DEV_STATUS_TIP_NOT_CONNECTED, style: "warning" },
                { tooltip: Strings.LIVE_DEV_STATUS_TIP_NOT_CONNECTED, style: "" },
                { tooltip: Strings.LIVE_DEV_STATUS_TIP_PROGRESS1, style: "info" },
                { tooltip: Strings.LIVE_DEV_STATUS_TIP_CONNECTED, style: "success" },
                { tooltip: Strings.LIVE_DEV_STATUS_TIP_OUT_OF_SYNC, style: "out-of-sync" },
                { tooltip: Strings.LIVE_DEV_STATUS_TIP_SYNC_ERROR, style: "sync-error" },
                { tooltip: Strings.LIVE_DEV_STATUS_TIP_PROGRESS1, style: "info" },
                { tooltip: Strings.LIVE_DEV_STATUS_TIP_PROGRESS1, style: "info" }
            ];
        } else {
            LiveDevImpl = LiveDevelopment;
            _status = [
                { tooltip: Strings.LIVE_DEV_STATUS_TIP_NOT_CONNECTED, style: "warning" },
                { tooltip: Strings.LIVE_DEV_STATUS_TIP_NOT_CONNECTED, style: "" },
                { tooltip: Strings.LIVE_DEV_STATUS_TIP_PROGRESS1, style: "info" },
                { tooltip: Strings.LIVE_DEV_STATUS_TIP_PROGRESS2, style: "info" },
                { tooltip: Strings.LIVE_DEV_STATUS_TIP_CONNECTED, style: "success" },
                { tooltip: Strings.LIVE_DEV_STATUS_TIP_OUT_OF_SYNC, style: "out-of-sync" },
                { tooltip: Strings.LIVE_DEV_STATUS_TIP_SYNC_ERROR, style: "sync-error" }
            ];
        }
    }

    /** Setup window references to useful LiveDevelopment modules */
    function _setupDebugHelpers() {
        window.ld = LiveDevelopment;
        window.i = Inspector;
        window.report = function report(params) { window.params = params; console.info(params); };
    }

    /** force reload the live preview */
    function _handleReloadLivePreviewCommand() {
        if (LiveDevelopment.status >= LiveDevelopment.STATUS_ACTIVE) {
            LiveDevelopment.reload();
        }
    }

    /** Initialize LiveDevelopment */
    AppInit.appReady(function () {
        params.parse();

        Inspector.init(config);
        LiveDevelopment.init(config);

        // init experimental multi-browser implementation
        // it can be enable by setting 'livedev.multibrowser' preference to true.
        // It has to be initiated at this point in case of dynamically switching
        // by changing the preference value.
        MultiBrowserLiveDev.init(config);

        _loadStyles();
        _setupGoLiveButton();
        _setupGoLiveMenu();

        _updateHighlightCheckmark();

        if (config.debug) {
            _setupDebugHelpers();
        }

        // trigger autoconnect
        if (config.autoconnect &&
                window.sessionStorage.getItem("live.enabled") === "true" &&
                DocumentManager.getCurrentDocument()) {
            _handleGoLiveCommand();
        }

        // Redraw highlights when window gets focus. This ensures that the highlights
        // will be in sync with any DOM changes that may have occurred.
        $(window).focus(function () {
            if (Inspector.connected() && config.highlight) {
                LiveDevelopment.redrawHighlight();
            }
        });
    });

    // init prefs
    PreferencesManager.stateManager.definePreference("livedev.highlight", "boolean", true)
        .on("change", function () {
            config.highlight = PreferencesManager.getViewState("livedev.highlight");
            _updateHighlightCheckmark();
        });

    PreferencesManager.convertPreferences(module, {
        "highlight": "user livedev.highlight",
        "afterFirstLaunch": "user livedev.afterFirstLaunch"
    }, true);

    PreferencesManager.definePreference("livedev.multibrowser", "boolean", false)
        .on("change", function () {
            // stop the current session if it is open and set implementation based on
            // the pref value. It could be automaticallty restarted but, since the preference file,
            // is the document open in the editor, it will no launch a live document.
            if (LiveDevImpl && LiveDevImpl.status >= LiveDevImpl.STATUS_ACTIVE) {
                LiveDevImpl.close();
                // status changes will now be listen from the new implementation
                LiveDevImpl.off('statusChange');
            }
            _setImplementation(PreferencesManager.get('livedev.multibrowser'));
            // setup status changes listeners for new implementation
            _setupGoLiveButton();
            _setupGoLiveMenu();
        });

    config.highlight = PreferencesManager.getViewState("livedev.highlight");

    // init commands
    CommandManager.register(Strings.CMD_LIVE_FILE_PREVIEW,  Commands.FILE_LIVE_FILE_PREVIEW, _handleGoLiveCommand);
    CommandManager.register(Strings.CMD_LIVE_HIGHLIGHT, Commands.FILE_LIVE_HIGHLIGHT, _handlePreviewHighlightCommand);
    CommandManager.register(Strings.CMD_RELOAD_LIVE_PREVIEW, Commands.CMD_RELOAD_LIVE_PREVIEW, _handleReloadLivePreviewCommand);
    CommandManager.get(Commands.FILE_LIVE_HIGHLIGHT).setEnabled(false);

    // Export public functions
});
<|endoftext|>