<commit_before>import macro from 'vtk.js/Sources/macro';
import vtkRenderer from 'vtk.js/Sources/Rendering/Core/Renderer';
import Constants from 'vtk.js/Sources/Interaction/Widgets/OrientationMarkerWidget/Constants';

const { vtkErrorMacro } = macro;
const { Corners } = Constants;

// ----------------------------------------------------------------------------
// vtkOrientationMarkerWidget
// ----------------------------------------------------------------------------

function vtkOrientationMarkerWidget(publicAPI, model) {
  // Set our className
  model.classHierarchy.push('vtkOrientationMarkerWidget');

  const superClass = { ...publicAPI };

  // Private variables

  const previousCameraInput = [];
  const selfRenderer = vtkRenderer.newInstance();
  let interactorUnsubscribe = null;
  let viewUnsubscribe = null;
  let selfSubscription = null;

  publicAPI.computeViewport = () => {
    const [viewXSize, viewYSize] = model.interactor.getView().getSize();
    const minViewSize = Math.min(viewXSize, viewYSize);

    let pixelSize = model.viewportSize * minViewSize;
    // clamp pixel size
    pixelSize = Math.max(
      Math.min(model.minPixelSize, minViewSize),
      Math.min(model.maxPixelSize, pixelSize)
    );

    const xFrac = pixelSize / viewXSize;
    const yFrac = pixelSize / viewYSize;
    // [left bottom right top]
    switch (model.viewportCorner) {
      case Corners.TOP_LEFT:
        return [0, 1 - yFrac, xFrac, 1];
      case Corners.TOP_RIGHT:
        return [1 - xFrac, 1 - yFrac, 1, 1];
      case Corners.BOTTOM_LEFT:
        return [0, 0, xFrac, yFrac];
      case Corners.BOTTOM_RIGHT:
        return [1 - xFrac, 0, 1, yFrac];
      default:
        vtkErrorMacro('Invalid widget corner');
        return null;
    }
  };

  publicAPI.updateViewport = () => {
    selfRenderer.setViewport(...publicAPI.computeViewport());
    model.interactor.render();
  };

  publicAPI.updateMarkerOrientation = () => {
    const currentCamera = model.interactor
      .findPokedRenderer()
      .getActiveCamera();

    if (!currentCamera) {
      return;
    }

    const position = currentCamera.getReferenceByName('position');
    const focalPoint = currentCamera.getReferenceByName('focalPoint');
    const viewUp = currentCamera.getReferenceByName('viewUp');
    if (
      previousCameraInput[0] !== position[0] ||
      previousCameraInput[1] !== position[1] ||
      previousCameraInput[2] !== position[2] ||
      previousCameraInput[3] !== focalPoint[0] ||
      previousCameraInput[4] !== focalPoint[1] ||
      previousCameraInput[5] !== focalPoint[2] ||
      previousCameraInput[6] !== viewUp[0] ||
      previousCameraInput[7] !== viewUp[1] ||
      previousCameraInput[8] !== viewUp[2]
    ) {
      previousCameraInput[0] = position[0];
      previousCameraInput[1] = position[1];
      previousCameraInput[2] = position[2];
      previousCameraInput[3] = focalPoint[0];
      previousCameraInput[4] = focalPoint[1];
      previousCameraInput[5] = focalPoint[2];
      previousCameraInput[6] = viewUp[0];
      previousCameraInput[7] = viewUp[1];
      previousCameraInput[8] = viewUp[2];
      const activeCamera = selfRenderer.getActiveCamera();
      activeCamera.setPosition(position[0], position[1], position[2]);
      activeCamera.setFocalPoint(focalPoint[0], focalPoint[1], focalPoint[2]);
      activeCamera.setViewUp(viewUp[0], viewUp[1], viewUp[2]);
      selfRenderer.resetCamera();
    }
  };

  /**
   * Enables/Disables the orientation marker.
   */
  publicAPI.setEnabled = (enabling) => {
    if (enabling) {
      if (model.enabled) {
        return;
      }

      if (!model.actor) {
        vtkErrorMacro('Must set actor before enabling orientation marker.');
        return;
      }

      if (!model.interactor) {
        vtkErrorMacro(
          'Must set interactor before enabling orientation marker.'
        );
        return;
      }

      const renderWindow = model.interactor
        .findPokedRenderer()
        .getRenderWindow();
      renderWindow.addRenderer(selfRenderer);
      if (renderWindow.getNumberOfLayers() < 2) {
        renderWindow.setNumberOfLayers(2);
      }
      // Highest number is foreground
      selfRenderer.setLayer(renderWindow.getNumberOfLayers() - 1);
      selfRenderer.setInteractive(false);

      selfRenderer.addViewProp(model.actor);
      model.actor.setVisibility(true);

      ({ unsubscribe: interactorUnsubscribe } = model.interactor.onAnimation(
        publicAPI.updateMarkerOrientation
      ));

      ({ unsubscribe: viewUnsubscribe } = model.interactor
        .getView()
        .onModified(publicAPI.updateViewport));

      publicAPI.updateViewport();
      publicAPI.updateMarkerOrientation();

      model.enabled = true;
    } else {
      if (!model.enabled) {
        return;
      }
      model.enabled = false;

      viewUnsubscribe();
      viewUnsubscribe = null;
      interactorUnsubscribe();
      interactorUnsubscribe = null;

      model.actor.setVisibility(false);
      selfRenderer.removeViewProp(model.actor);

      const renderWindow = model.interactor
        .findPokedRenderer()
        .getRenderWindow();
      if (renderWindow) {
        renderWindow.removeRenderer(selfRenderer);
      }
    }
    publicAPI.modified();
  };

  /**
   * Sets the viewport corner.
   */
  publicAPI.setViewportCorner = (corner) => {
    if (corner === model.viewportCorner) {
      return;
    }

    model.viewportCorner = corner;
    if (model.enabled) {
      publicAPI.updateViewport();
    }
  };

  /**
   * Sets the viewport size.
   */
  publicAPI.setViewportSize = (sizeFactor) => {
    const viewportSize = Math.min(1, Math.max(0, sizeFactor));
    if (viewportSize === model.viewportSize) {
      return;
    }

    model.viewportSize = viewportSize;
    if (model.enabled) {
      publicAPI.updateViewport();
    }
  };

  publicAPI.setActor = (actor) => {
    const previousState = model.enabled;
    publicAPI.setEnabled(false);
    model.actor = actor;
    publicAPI.setEnabled(previousState);
  };

  publicAPI.getRenderer = () => selfRenderer;

  publicAPI.delete = () => {
    superClass.delete();
    if (selfSubscription) {
      selfSubscription.unsubscribe();
      selfSubscription = null;
    }
    if (interactorUnsubscribe) {
      interactorUnsubscribe();
      interactorUnsubscribe = null;
    }
    if (viewUnsubscribe) {
      viewUnsubscribe();
      viewUnsubscribe = null;
    }
  };

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

  // update viewport whenever we are updated
  selfSubscription = publicAPI.onModified(publicAPI.updateViewport);
}

// ----------------------------------------------------------------------------
// Object factory
// ----------------------------------------------------------------------------

export const DEFAULT_VALUES = {
  // actor: null,
  // interactor: null,
  viewportCorner: Constants.Corners.BOTTOM_LEFT,
  viewportSize: 0.2,
  minPixelSize: 50,
  maxPixelSize: 200,
};

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

export function extend(publicAPI, model, initialValues = {}) {
  Object.assign(model, DEFAULT_VALUES, initialValues);

  // Build VTK API
  macro.obj(publicAPI, model);

  macro.get(publicAPI, model, ['enabled', 'viewportCorner', 'viewportSize']);

  // NOTE: setting these while the widget is enabled will
  // not update the widget.
  macro.setGet(publicAPI, model, [
    'interactor',
    'minPixelSize',
    'maxPixelSize',
  ]);
  macro.get(publicAPI, model, ['actor']);

  // Object methods
  vtkOrientationMarkerWidget(publicAPI, model);
}

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

export const newInstance = macro.newInstance(
  extend,
  'vtkOrientationMarkerWidget'
);

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

export default { newInstance, extend, ...Constants };
<commit_msg>fix(OrientationMarkerWidget): Use resize observer<commit_after>import macro from 'vtk.js/Sources/macro';
import vtkRenderer from 'vtk.js/Sources/Rendering/Core/Renderer';
import Constants from 'vtk.js/Sources/Interaction/Widgets/OrientationMarkerWidget/Constants';

const { vtkErrorMacro } = macro;
const { Corners } = Constants;

// ----------------------------------------------------------------------------
// vtkOrientationMarkerWidget
// ----------------------------------------------------------------------------

function vtkOrientationMarkerWidget(publicAPI, model) {
  // Set our className
  model.classHierarchy.push('vtkOrientationMarkerWidget');

  const superClass = { ...publicAPI };

  // Private variables

  const previousCameraInput = [];
  const selfRenderer = vtkRenderer.newInstance();
  const resizeObserver = new ResizeObserver((entries) => {
    if (entries.length === 1) {
      publicAPI.updateViewport();
    }
  });
  let interactorUnsubscribe = null;
  let selfSubscription = null;

  publicAPI.computeViewport = () => {
    const [viewXSize, viewYSize] = model.interactor.getView().getSize();
    const minViewSize = Math.min(viewXSize, viewYSize);

    let pixelSize = model.viewportSize * minViewSize;
    // clamp pixel size
    pixelSize = Math.max(
      Math.min(model.minPixelSize, minViewSize),
      Math.min(model.maxPixelSize, pixelSize)
    );

    const xFrac = pixelSize / viewXSize;
    const yFrac = pixelSize / viewYSize;
    // [left bottom right top]
    switch (model.viewportCorner) {
      case Corners.TOP_LEFT:
        return [0, 1 - yFrac, xFrac, 1];
      case Corners.TOP_RIGHT:
        return [1 - xFrac, 1 - yFrac, 1, 1];
      case Corners.BOTTOM_LEFT:
        return [0, 0, xFrac, yFrac];
      case Corners.BOTTOM_RIGHT:
        return [1 - xFrac, 0, 1, yFrac];
      default:
        vtkErrorMacro('Invalid widget corner');
        return null;
    }
  };

  publicAPI.updateViewport = () => {
    selfRenderer.setViewport(...publicAPI.computeViewport());
    model.interactor.render();
  };

  publicAPI.updateMarkerOrientation = () => {
    const currentCamera = model.interactor
      .findPokedRenderer()
      .getActiveCamera();

    if (!currentCamera) {
      return;
    }

    const position = currentCamera.getReferenceByName('position');
    const focalPoint = currentCamera.getReferenceByName('focalPoint');
    const viewUp = currentCamera.getReferenceByName('viewUp');
    if (
      previousCameraInput[0] !== position[0] ||
      previousCameraInput[1] !== position[1] ||
      previousCameraInput[2] !== position[2] ||
      previousCameraInput[3] !== focalPoint[0] ||
      previousCameraInput[4] !== focalPoint[1] ||
      previousCameraInput[5] !== focalPoint[2] ||
      previousCameraInput[6] !== viewUp[0] ||
      previousCameraInput[7] !== viewUp[1] ||
      previousCameraInput[8] !== viewUp[2]
    ) {
      previousCameraInput[0] = position[0];
      previousCameraInput[1] = position[1];
      previousCameraInput[2] = position[2];
      previousCameraInput[3] = focalPoint[0];
      previousCameraInput[4] = focalPoint[1];
      previousCameraInput[5] = focalPoint[2];
      previousCameraInput[6] = viewUp[0];
      previousCameraInput[7] = viewUp[1];
      previousCameraInput[8] = viewUp[2];
      const activeCamera = selfRenderer.getActiveCamera();
      activeCamera.setPosition(position[0], position[1], position[2]);
      activeCamera.setFocalPoint(focalPoint[0], focalPoint[1], focalPoint[2]);
      activeCamera.setViewUp(viewUp[0], viewUp[1], viewUp[2]);
      selfRenderer.resetCamera();
    }
  };

  /**
   * Enables/Disables the orientation marker.
   */
  publicAPI.setEnabled = (enabling) => {
    if (enabling) {
      if (model.enabled) {
        return;
      }

      if (!model.actor) {
        vtkErrorMacro('Must set actor before enabling orientation marker.');
        return;
      }

      if (!model.interactor) {
        vtkErrorMacro(
          'Must set interactor before enabling orientation marker.'
        );
        return;
      }

      const renderWindow = model.interactor
        .findPokedRenderer()
        .getRenderWindow();
      renderWindow.addRenderer(selfRenderer);
      if (renderWindow.getNumberOfLayers() < 2) {
        renderWindow.setNumberOfLayers(2);
      }
      // Highest number is foreground
      selfRenderer.setLayer(renderWindow.getNumberOfLayers() - 1);
      selfRenderer.setInteractive(false);

      selfRenderer.addViewProp(model.actor);
      model.actor.setVisibility(true);

      ({ unsubscribe: interactorUnsubscribe } = model.interactor.onAnimation(
        publicAPI.updateMarkerOrientation
      ));

      resizeObserver.observe(model.interactor.getView().getCanvas());

      publicAPI.updateViewport();
      publicAPI.updateMarkerOrientation();

      model.enabled = true;
    } else {
      if (!model.enabled) {
        return;
      }
      model.enabled = false;

      resizeObserver.disconnect();
      interactorUnsubscribe();
      interactorUnsubscribe = null;

      model.actor.setVisibility(false);
      selfRenderer.removeViewProp(model.actor);

      const renderWindow = model.interactor
        .findPokedRenderer()
        .getRenderWindow();
      if (renderWindow) {
        renderWindow.removeRenderer(selfRenderer);
      }
    }
    publicAPI.modified();
  };

  /**
   * Sets the viewport corner.
   */
  publicAPI.setViewportCorner = (corner) => {
    if (corner === model.viewportCorner) {
      return;
    }

    model.viewportCorner = corner;
    if (model.enabled) {
      publicAPI.updateViewport();
    }
  };

  /**
   * Sets the viewport size.
   */
  publicAPI.setViewportSize = (sizeFactor) => {
    const viewportSize = Math.min(1, Math.max(0, sizeFactor));
    if (viewportSize === model.viewportSize) {
      return;
    }

    model.viewportSize = viewportSize;
    if (model.enabled) {
      publicAPI.updateViewport();
    }
  };

  publicAPI.setActor = (actor) => {
    const previousState = model.enabled;
    publicAPI.setEnabled(false);
    model.actor = actor;
    publicAPI.setEnabled(previousState);
  };

  publicAPI.getRenderer = () => selfRenderer;

  publicAPI.delete = () => {
    superClass.delete();
    if (selfSubscription) {
      selfSubscription.unsubscribe();
      selfSubscription = null;
    }
    if (interactorUnsubscribe) {
      interactorUnsubscribe();
      interactorUnsubscribe = null;
    }
    resizeObserver.disconnect();
  };

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

  // update viewport whenever we are updated
  selfSubscription = publicAPI.onModified(publicAPI.updateViewport);
}

// ----------------------------------------------------------------------------
// Object factory
// ----------------------------------------------------------------------------

export const DEFAULT_VALUES = {
  // actor: null,
  // interactor: null,
  viewportCorner: Constants.Corners.BOTTOM_LEFT,
  viewportSize: 0.2,
  minPixelSize: 50,
  maxPixelSize: 200,
};

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

export function extend(publicAPI, model, initialValues = {}) {
  Object.assign(model, DEFAULT_VALUES, initialValues);

  // Build VTK API
  macro.obj(publicAPI, model);

  macro.get(publicAPI, model, ['enabled', 'viewportCorner', 'viewportSize']);

  // NOTE: setting these while the widget is enabled will
  // not update the widget.
  macro.setGet(publicAPI, model, [
    'interactor',
    'minPixelSize',
    'maxPixelSize',
  ]);
  macro.get(publicAPI, model, ['actor']);

  // Object methods
  vtkOrientationMarkerWidget(publicAPI, model);
}

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

export const newInstance = macro.newInstance(
  extend,
  'vtkOrientationMarkerWidget'
);

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

export default { newInstance, extend, ...Constants };
<|endoftext|>


<commit_before>function draw(){
    buffer.clearRect(0,0,get('buffer').width,get('buffer').height);

    buffer.strokeStyle='#fff';
    if(mouse_down==1){
        buffer.beginPath();
        buffer.rect(mouse_lock_x,mouse_lock_y,mouse_x-mouse_lock_x,mouse_y-mouse_lock_y);
        buffer.closePath();
        buffer.stroke()
    }

    i=boxen.length-1;
    if(i>=0){
        do{
            buffer.beginPath();
            buffer.rect(boxen[i][0],boxen[i][1],boxen[i][2],boxen[i][3]);
            buffer.closePath();
            buffer.stroke()
        }while(i--)
    }

    buffer.font='23pt sans-serif';
    buffer.textAlign='center';
    buffer.fillStyle='#fff';
    buffer.fillText('Click+Drag! ESC=clear',x,50);

    canvas.clearRect(0,0,get('canvas').width,get('canvas').height);
    canvas.drawImage(get('buffer'),0,0)
}
function get(i){
    return document.getElementById(i)
}
function resize(){
    get('buffer').width=get('canvas').width=window.innerWidth;
    get('buffer').height=get('canvas').height=window.innerHeight;
    x=get('canvas').width/2;
    y=get('canvas').height/2
}
var buffer=canvas=i=mouse_down=mouse_x=mouse_y=x=y=0,
mouse_lock_x=mouse_lock_y=-1,
boxen=[];

buffer=get('buffer').getContext('2d');
canvas=get('canvas').getContext('2d');

resize();

setInterval('draw()',30);

window.onkeydown=function(e){
    i=window.event?event:e;
    i=i.charCode?i.charCode:i.keyCode;
    if(i==27){/*ESC*/
        boxen=[]
    }
};
window.onmousedown=function(e){
    e.preventDefault();
    if(e.button==0){/*Left Click*/
        mouse_down=1;
        mouse_x=e.pageX;
        mouse_y=e.pageY;
        mouse_lock_x=mouse_x;
        mouse_lock_y=mouse_y
    }
};
window.onmousemove=function(e){
    if(mouse_down>0){
        mouse_x=e.pageX;
        mouse_y=e.pageY
    }
};
window.onmouseup=function(){
    mouse_down=0;
    if(mouse_x-mouse_lock_x!=0||mouse_y-mouse_lock_y!=0){
        boxen.push([mouse_lock_x,mouse_lock_y,mouse_x-mouse_lock_x,mouse_y-mouse_lock_y])
    }
};

window.onresize=resize
<commit_msg>improved formatting<commit_after>function draw(){
    buffer.clearRect(0,0,get('buffer').width,get('buffer').height);

    buffer.strokeStyle='#fff';

    /*if mouse down, draw current box*/
    if(mouse_down===1){
        buffer.beginPath();
        buffer.rect(
            mouse_lock_x,
            mouse_lock_y,
            mouse_x - mouse_lock_x,
            mouse_y - mouse_lock_y
        );
        buffer.closePath();
        buffer.stroke()
    }

    /*draw saved boxes*/
    i = boxen.length-1;
    if(i>=0){
        do{
            buffer.beginPath();
            buffer.rect(
                boxen[i][0],
                boxen[i][1],
                boxen[i][2],
                boxen[i][3]
            );
            buffer.closePath();
            buffer.stroke()
        }while(i--)
    }

    buffer.font = '23pt sans-serif';
    buffer.textAlign = 'center';
    buffer.fillStyle = '#fff';
    buffer.fillText(
        'Click+Drag! ESC=clear',
        x,
        50
    );

    canvas.clearRect(0,0,get('canvas').width,get('canvas').height);
    canvas.drawImage(get('buffer'),0,0)
}
function get(i){
    return document.getElementById(i)
}
function resize(){
    get('buffer').width = get('canvas').width = window.innerWidth;
    get('buffer').height = get('canvas').heigh t= window.innerHeight;

    x = get('canvas').width/2;
    y = get('canvas').height/2
}
var boxen = [];
var buffer = get('buffer').getContext('2d');
var canvas = get('canvas').getContext('2d');
var i = 0;
var mouse_down = 0;
var mouse_lock_x = -1;
var mouse_lock_y = -1;
var mouse_x = 0;
var mouse_y = 0;
var x = 0;
var y = 0;

resize();

setInterval('draw()',30);

window.onkeydown=function(e){
    i=window.event?event:e;
    i=i.charCode?i.charCode:i.keyCode;
    if(i===27){/*ESC*/
        /*delete all boxes*/
        boxen = []
    }
};
window.onmousedown=function(e){
    e.preventDefault();
    if(e.button===0){/*Left Click*/
        /*start new box*/
        mouse_down = 1;
        mouse_x = e.pageX;
        mouse_y = e.pageY;
        mouse_lock_x = mouse_x;
        mouse_lock_y = mouse_y
    }
};
window.onmousemove=function(e){
    /*if mouse is down, update current box*/
    if(mouse_down>0){
        mouse_x = e.pageX;
        mouse_y = e.pageY
    }
};
window.onmouseup=function(){
    mouse_down = 0;
    if(mouse_x-mouse_lock_x!=0 || mouse_y-mouse_lock_y!=0){
        /*add current box to array of boxes*/
        boxen.push([
            mouse_lock_x,/*top left x*/
            mouse_lock_y,/*top left y*/
            mouse_x - mouse_lock_x,/*width*/
            mouse_y - mouse_lock_y/*height*/
        ])
    }
};

window.onresize=resize
<|endoftext|>


<commit_before>import { Messages } from '../../../Core/Js/Components/Messages'
import { Meta } from '../../../Core/Js/Components/Meta'

export class Faq {
  constructor () {
    // index stuff
    if ($('[data-sequence-drag-and-drop="data-grid-faq"]').length > 0) {
      // drag and drop
      this.bindDragAndDropQuestions()
      this.checkForEmptyCategories()
    }

    // do meta
    if ($('#title').length > 0) Meta.doMeta(('#title'))

    // hide the data
    $('.longFeedback').hide()

    $('[data-role=delete-feedback]').on('click', this.deleteFeedbackClick)
  }

  /**
   * Check for empty categories and make it still possible to drop questions
   */
  checkForEmptyCategories () {
    // reset initial empty grids
    $('table.emptyGrid').each((index, table) => {
      $(table).find('td').parent().remove()
      $(table).append(
        '
' +
        '' + window.backend.locale.msg('NoQuestionInCategory') + '	' +
        '
'
      )
      $(table).removeClass('emptyGrid')
    })

    // when there are empty categories
    if ($('tr.noQuestions').length > 0) {
      // make dataGrid droppable
      $('table.jsDataGrid').droppable({
        // only accept table rows
        accept: 'table.jsDataGrid tr',
        drop (e, ui) {
          // remove the no questions in category message
          $(this).find('tr.noQuestions').remove()
        }
      })

      // cleanup remaining no questions
      $('table.jsDataGrid').each((index, table) => {
        if ($(table).find('tr').length > 2) $(table).find('tr.noQuestions').remove()
      })
    }
  }

  saveNewQuestionSequence ($wrapper, questionId, toCategoryId) {
    // vars we will need
    const fromCategoryId = $wrapper.attr('id').substring(9)
    const fromCategorySequence = $wrapper.sortable('toArray').join(',')
    const toCategorySequence = $('#dataGrid-' + toCategoryId).sortable('toArray').join(',')

    // make ajax call
    $.ajax({
      data: {
        fork: {action: 'SequenceQuestions'},
        questionId: questionId,
        fromCategoryId: fromCategoryId,
        toCategoryId: toCategoryId,
        fromCategorySequence: fromCategorySequence,
        toCategorySequence: toCategorySequence
      },
      success: (data, textStatus) => {
        // successfully saved reordering sequence
        if (data.code === 200) {
          var $fromWrapper = $('div#dataGrid-' + fromCategoryId)
          var $fromWrapperTitle = $fromWrapper.find('.content-title h2')

          var $toWrapper = $('div#dataGrid-' + toCategoryId)
          var $toWrapperTitle = $toWrapper.find('.content-title h2')

          // change count in title of from wrapper (if any)
          $fromWrapperTitle.html($fromWrapperTitle.html().replace(/\(([0-9]*)\)$/, '(' + ($fromWrapper.find('table.jsDataGrid tr').length - 1) + ')'))

          // if there are no records -> show message
          if ($('div#dataGrid-' + fromCategoryId + ' table.jsDataGrid tr').length === 1) {
            $('div#dataGrid-' + fromCategoryId + ' table.jsDataGrid').append('
' +
              '' + window.backend.locale.msg('NoQuestionInCategory') + '	' +
              '
'
            )
          }

          // check empty categories
          this.checkForEmptyCategories()

          // redo odd-even
          const table = $('table.jsDataGrid')
          table.find('tr').removeClass('odd').removeClass('even')
          table.find('tr:even').addClass('even')
          table.find('tr:odd').addClass('odd')

          // change count in title of to wrapper (if any)
          $toWrapperTitle.html($toWrapperTitle.html().replace(/\(([0-9]*)\)$/, '(' + ($toWrapper.find('table.jsDataGrid tr').length - 1) + ')'))

          // show message
          Messages.add('success', data.message)
        } else {
          // refresh page
          location.reload()

          // show message
          Messages.add('danger', 'alter sequence failed.')
        }

        // alert the user
        if (data.code !== 200 && jsBackend.debug) { window.alert(data.message) }
      },
      error (XMLHttpRequest, textStatus, errorThrown) {
        // revert
        $(this).sortable('cancel')

        // show message
        Messages.add('danger', 'alter sequence failed.')

        // alert the user
        if (jsBackend.debug) { window.alert(textStatus) }
      }
    })
  }

  /**
   * Bind drag and dropping of a category
   */
  bindDragAndDropQuestions () {
    // go over every dataGrid
    $.each($('div.jsDataGridQuestionsHolder'), (index, element) => {
      const $this = $(element)
      // make them sortable
      $this.sortable({
        items: 'table.jsDataGrid tbody tr',        // set the elements that user can sort
        handle: 'td.dragAndDropHandle',            // set the element that user can grab
        tolerance: 'pointer',                    // give a more natural feeling
        connectWith: 'div.jsDataGridQuestionsHolder',        // this is what makes dragging between categories possible
        stop: (e, ui) => {
          this.saveNewQuestionSequence(
            $(element),
            ui.item.attr('id'),
            ui.item.parents('.jsDataGridQuestionsHolder').attr('id').substring(9)
          )
        }
      })
      $this.find('[data-role="order-move"]').off('click.fork.order-move').on('click.fork.order-move', (e) => {
        const $this = $(e.currentTarget)
        const $row = $this.closest('tr')
        const direction = $this.data('direction')
        const $holder = $row.closest('.jsDataGridQuestionsHolder')

        if (direction === 'up') {
          $row.prev().insertAfter($row)
        } else if (direction === 'down') {
          $row.next().insertBefore($row)
        }

        this.saveNewQuestionSequence($holder, $row.attr('id'), $holder.attr('id').substring(9))
      })
    })
  }

  deleteFeedbackClick (event) {
    event.preventDefault()

    const $modal = $('#confirmDeleteFeedback')
    $modal.siblings('#delete_id').val($(event.currentTarget).data('id'))
    $modal.modal('show')
  }
}
<commit_msg>Make faq sortable work with es6<commit_after>import { Messages } from '../../../Core/Js/Components/Messages'
import { Meta } from '../../../Core/Js/Components/Meta'
import Sortable from 'sortablejs'
import { Config } from '../../../Core/Js/Components/Config'

export class Faq {
  constructor () {
    // index stuff
    if ($('[data-sequence-drag-and-drop="data-grid-faq"]').length > 0) {
      // drag and drop
      this.bindDragAndDropQuestions()
      this.checkForEmptyCategories()
    }

    // do meta
    if ($('#title').length > 0) Meta.doMeta(('#title'))
  }

  /**
   * Check for empty categories and make it still possible to drop questions
   */
  checkForEmptyCategories () {
    // reset initial empty grids
    $('table.emptyGrid').each((index, grid) => {
      $(grid).find('td').parent().remove()
      $(grid).append(
        '
' +
        '' + window.backend.locale.msg('NoQuestionInCategory') + '	' +
        '
'
      )
      $(grid).removeClass('emptyGrid')
    })

    // when there are empty categories
    if ($('tr.noQuestions').length > 0) {
      // cleanup remaining no questions
      $('table.jsDataGrid').each((index, grid) => {
        if ($(grid).find('tr').length > 2) $(grid).find('tr.noQuestions').remove()
      })
    }
  }

  saveNewQuestionSequence (questionId, fromCategoryId, toCategoryId, fromCategorySequence, toCategorySequence) {
    // make ajax call
    $.ajax({
      data: {
        fork: {action: 'SequenceQuestions'},
        questionId: questionId,
        fromCategoryId: fromCategoryId,
        toCategoryId: toCategoryId,
        fromCategorySequence: fromCategorySequence,
        toCategorySequence: toCategorySequence
      },
      success: (data, textStatus) => {
        // successfully saved reordering sequence
        if (data.code === 200) {
          const $fromWrapper = $('div#dataGrid-' + fromCategoryId)
          const $fromWrapperTitle = $fromWrapper.find('.content-title h2')

          const $toWrapper = $('div#dataGrid-' + toCategoryId)
          const $toWrapperTitle = $toWrapper.find('.content-title h2')

          // change count in title of from wrapper (if any)
          $fromWrapperTitle.html($fromWrapperTitle.html().replace(/\(([0-9]*)\)$/, '(' + ($fromWrapper.find('table.jsDataGrid tr').length - 1) + ')'))

          // if there are no records -> show message
          if ($fromWrapper.find('table.jsDataGrid tr').length === 1) {
            $fromWrapper.find('table.jsDataGrid').append('' +
              '
' +
              '' + window.backend.locale.msg('NoQuestionInCategory') + '	' +
              '
'
            )
          }

          // check empty categories
          this.checkForEmptyCategories()

          // redo odd-even
          const table = $('table.jsDataGrid')
          table.find('tr').removeClass('odd').removeClass('even')
          table.find('tr:even').addClass('even')
          table.find('tr:odd').addClass('odd')

          // change count in title of to wrapper (if any)
          $toWrapperTitle.html($toWrapperTitle.html().replace(/\(([0-9]*)\)$/, '(' + ($toWrapper.find('table.jsDataGrid tr').length - 1) + ')'))

          // show message
          Messages.add('success', data.message)
        } else {
          // refresh page
          location.reload()

          // show message
          Messages.add('danger', 'alter sequence failed.')
        }

        // alert the user
        if (data.code !== 200 && Config.isDebug()) { window.alert(data.message) }
      },
      error (XMLHttpRequest, textStatus, errorThrown) {
        // refresh page
        location.reload()

        // show message
        Messages.add('danger', 'alter sequence failed.')

        // alert the user
        if (Config.isDebug()) { window.alert(textStatus) }
      }
    })
  }

  /**
   * Bind drag and dropping of a category
   */
  bindDragAndDropQuestions () {
    // go over every dataGrid
    $.each($('[data-sequence-drag-and-drop="data-grid-faq"] tbody'), (index, element) => {
      // make them sortable
      new Sortable(element, {
        group: 'faqIndex', // this is what makes dragging between categories possible
        onEnd: (event) => {
          window.backend.faq.saveNewQuestionSequence(
            $(event.item).attr('id'),
            $(event.from).parents('[data-questions-holder]').attr('id').substring(9),
            $(event.to).parents('[data-questions-holder]').attr('id').substring(9),
            window.backend.faq.getSequence($(event.from)),
            window.backend.faq.getSequence($(event.to))
          )
        }
      })

      $(element).find('[data-role="order-move"]').off('click.fork.order-move').on('click.fork.order-move', (e) => {
        this.bindArrowMoveQuestions(e, element)
      })
    })
  }

  bindArrowMoveQuestions (e, element) {
    const $current = $(e.currentTarget)
    const $row = $current.closest('tr')
    const direction = $current.data('direction')
    const questionId = $row.attr('id')
    const fromCategoryId = $current.parents('[data-questions-holder]').attr('id').substring(9)
    const toCategoryId = fromCategoryId
    const fromCategorySequence = this.getSequence($(element))

    if (direction === 'up') {
      $row.prev().insertAfter($row)
    } else if (direction === 'down') {
      $row.next().insertBefore($row)
    }

    // set to category sequence after it's moved
    const toCategorySequence = this.getSequence($(element))

    this.saveNewQuestionSequence(
      questionId,
      fromCategoryId,
      toCategoryId,
      fromCategorySequence,
      toCategorySequence
    )
  }

  getSequence (wrapper) {
    const sequence = []
    const rows = $(wrapper).find('tr')

    $.each(rows, (index, element) => {
      const id = $(element).data('id')
      sequence.push(id)
    })

    return sequence.join(',')
  }

  deleteFeedbackClick (event) {
    event.preventDefault()

    const $modal = $('#confirmDeleteFeedback')
    $modal.siblings('#delete_id').val($(event.currentTarget).data('id'))
    $modal.modal('show')
  }
}
<|endoftext|>


<commit_before>import React from 'react';
import { connect } from 'react-redux';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import PropTypes from 'prop-types';
import StatusListContainer from 'flavours/glitch/features/ui/containers/status_list_container';
import Column from 'flavours/glitch/components/column';
import ColumnHeader from 'flavours/glitch/components/column_header';
import { expandCommunityTimeline } from 'flavours/glitch/actions/timelines';
import { addColumn, removeColumn, moveColumn } from 'flavours/glitch/actions/columns';
import ColumnSettingsContainer from './containers/column_settings_container';
import { connectCommunityStream } from 'flavours/glitch/actions/streaming';

const messages = defineMessages({
  title: { id: 'column.community', defaultMessage: 'Local timeline' },
});

const mapStateToProps = (state, { onlyMedia, columnId }) => {
  const uuid = columnId;
  const columns = state.getIn(['settings', 'columns']);
  const index = columns.findIndex(c => c.get('uuid') === uuid);

  return {
    hasUnread: state.getIn(['timelines', `community${onlyMedia ? ':media' : ''}`, 'unread']) > 0 || state.getIn(['timelines', `community${onlyMedia ? ':media' : ''}`, 'pendingItems']).size > 0,
    onlyMedia: (columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'onlyMedia']) : state.getIn(['settings', 'community', 'other', 'onlyMedia']),
  };
};

export default @connect(mapStateToProps)
@injectIntl
class CommunityTimeline extends React.PureComponent {

  static defaultProps = {
    onlyMedia: false,
  };

  static contextTypes = {
    router: PropTypes.object,
  };

  static propTypes = {
    dispatch: PropTypes.func.isRequired,
    columnId: PropTypes.string,
    intl: PropTypes.object.isRequired,
    hasUnread: PropTypes.bool,
    multiColumn: PropTypes.bool,
    onlyMedia: PropTypes.bool,
  };

  handlePin = () => {
    const { columnId, dispatch, onlyMedia } = this.props;

    if (columnId) {
      dispatch(removeColumn(columnId));
    } else {
      dispatch(addColumn('COMMUNITY', { other: { onlyMedia } }));
    }
  }

  handleMove = (dir) => {
    const { columnId, dispatch } = this.props;
    dispatch(moveColumn(columnId, dir));
  }

  handleHeaderClick = () => {
    this.column.scrollTop();
  }

  componentDidMount () {
    const { dispatch, onlyMedia } = this.props;

    dispatch(expandCommunityTimeline({ onlyMedia }));
    this.disconnect = dispatch(connectCommunityStream({ onlyMedia }));
  }

  componentDidUpdate (prevProps) {
    if (prevProps.onlyMedia !== this.props.onlyMedia) {
      const { dispatch, onlyMedia } = this.props;

      this.disconnect();
      dispatch(expandCommunityTimeline({ onlyMedia }));
      this.disconnect = dispatch(connectCommunityStream({ onlyMedia }));
    }
  }

  componentWillUnmount () {
    if (this.disconnect) {
      this.disconnect();
      this.disconnect = null;
    }
  }

  setRef = c => {
    this.column = c;
  }

  handleLoadMore = maxId => {
    const { dispatch, onlyMedia } = this.props;

    dispatch(expandCommunityTimeline({ maxId, onlyMedia }));
  }

  render () {
    const { intl, hasUnread, columnId, multiColumn, onlyMedia } = this.props;
    const pinned = !!columnId;

    return (
      <Column ref={this.setRef} name='local' label={intl.formatMessage(messages.title)}>
        <ColumnHeader
          icon='users'
          active={hasUnread}
          title={intl.formatMessage(messages.title)}
          onPin={this.handlePin}
          onMove={this.handleMove}
          onClick={this.handleHeaderClick}
          pinned={pinned}
          multiColumn={multiColumn}
        >
          <ColumnSettingsContainer columnId={columnId} />
        </ColumnHeader>

        <StatusListContainer
          trackScroll={!pinned}
          scrollKey={`community_timeline-${columnId}`}
          timelineId={`community${onlyMedia ? ':media' : ''}`}
          onLoadMore={this.handleLoadMore}
          emptyMessage={<FormattedMessage id='empty_column.community' defaultMessage='The local timeline is empty. Write something publicly to get the ball rolling!' />}
        />
      </Column>
    );
  }

}
<commit_msg>[Glitch] Check existence of timeline state in community timeline<commit_after>import React from 'react';
import { connect } from 'react-redux';
import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
import PropTypes from 'prop-types';
import StatusListContainer from 'flavours/glitch/features/ui/containers/status_list_container';
import Column from 'flavours/glitch/components/column';
import ColumnHeader from 'flavours/glitch/components/column_header';
import { expandCommunityTimeline } from 'flavours/glitch/actions/timelines';
import { addColumn, removeColumn, moveColumn } from 'flavours/glitch/actions/columns';
import ColumnSettingsContainer from './containers/column_settings_container';
import { connectCommunityStream } from 'flavours/glitch/actions/streaming';

const messages = defineMessages({
  title: { id: 'column.community', defaultMessage: 'Local timeline' },
});

const mapStateToProps = (state, { onlyMedia, columnId }) => {
  const uuid = columnId;
  const columns = state.getIn(['settings', 'columns']);
  const index = columns.findIndex(c => c.get('uuid') === uuid);
  const timelineState = state.getIn(['timelines', `community${onlyMedia ? ':media' : ''}`]);

  return {
    hasUnread: !!timelineState && (timelineState.get('unread') > 0 || timelineState.get('pendingItems').size > 0),
    onlyMedia: (columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'onlyMedia']) : state.getIn(['settings', 'community', 'other', 'onlyMedia']),
  };
};

export default @connect(mapStateToProps)
@injectIntl
class CommunityTimeline extends React.PureComponent {

  static defaultProps = {
    onlyMedia: false,
  };

  static contextTypes = {
    router: PropTypes.object,
  };

  static propTypes = {
    dispatch: PropTypes.func.isRequired,
    columnId: PropTypes.string,
    intl: PropTypes.object.isRequired,
    hasUnread: PropTypes.bool,
    multiColumn: PropTypes.bool,
    onlyMedia: PropTypes.bool,
  };

  handlePin = () => {
    const { columnId, dispatch, onlyMedia } = this.props;

    if (columnId) {
      dispatch(removeColumn(columnId));
    } else {
      dispatch(addColumn('COMMUNITY', { other: { onlyMedia } }));
    }
  }

  handleMove = (dir) => {
    const { columnId, dispatch } = this.props;
    dispatch(moveColumn(columnId, dir));
  }

  handleHeaderClick = () => {
    this.column.scrollTop();
  }

  componentDidMount () {
    const { dispatch, onlyMedia } = this.props;

    dispatch(expandCommunityTimeline({ onlyMedia }));
    this.disconnect = dispatch(connectCommunityStream({ onlyMedia }));
  }

  componentDidUpdate (prevProps) {
    if (prevProps.onlyMedia !== this.props.onlyMedia) {
      const { dispatch, onlyMedia } = this.props;

      this.disconnect();
      dispatch(expandCommunityTimeline({ onlyMedia }));
      this.disconnect = dispatch(connectCommunityStream({ onlyMedia }));
    }
  }

  componentWillUnmount () {
    if (this.disconnect) {
      this.disconnect();
      this.disconnect = null;
    }
  }

  setRef = c => {
    this.column = c;
  }

  handleLoadMore = maxId => {
    const { dispatch, onlyMedia } = this.props;

    dispatch(expandCommunityTimeline({ maxId, onlyMedia }));
  }

  render () {
    const { intl, hasUnread, columnId, multiColumn, onlyMedia } = this.props;
    const pinned = !!columnId;

    return (
      <Column ref={this.setRef} name='local' label={intl.formatMessage(messages.title)}>
        <ColumnHeader
          icon='users'
          active={hasUnread}
          title={intl.formatMessage(messages.title)}
          onPin={this.handlePin}
          onMove={this.handleMove}
          onClick={this.handleHeaderClick}
          pinned={pinned}
          multiColumn={multiColumn}
        >
          <ColumnSettingsContainer columnId={columnId} />
        </ColumnHeader>

        <StatusListContainer
          trackScroll={!pinned}
          scrollKey={`community_timeline-${columnId}`}
          timelineId={`community${onlyMedia ? ':media' : ''}`}
          onLoadMore={this.handleLoadMore}
          emptyMessage={<FormattedMessage id='empty_column.community' defaultMessage='The local timeline is empty. Write something publicly to get the ball rolling!' />}
        />
      </Column>
    );
  }

}
<|endoftext|>


<commit_before>  /**
  * @author Christopher Klpap
  * @description This component displays the Verse so selection, edit and comments can be made
  */
import React from 'react';
import usfmjs from 'usfm-js';
import View from './components/View';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import {optimizeSelections, normalizeString} from './utils/selectionHelpers';

class VerseCheck extends React.Component {
  constructor(props) {
    super(props);
    const mode = props.selectionsReducer.selections.length > 0 ? 'default' : 'select';
    this.state = {
      mode: mode,
      comment: undefined,
      commentChanged: false,
      verseText: undefined,
      verseChanged: false,
      selections: [],
      tags: [],
      dialogModalVisibility: false,
      goToNextOrPrevious: null
    };

    let _this = this;

    this.tagList = [
      ["spelling", "Spelling"],
      ["punctuation", "Punctuation"],
      ["grammar", "Grammar"],
      ["meaning", "Meaning"],
      ["wordChoice", "Word Choice"],
      ["other", "Other"]
    ]

    this.actions = {
      handleGoToNext() {
        if (!_this.props.loginReducer.loggedInUser) {
          _this.props.actions.selectModalTab(1, 1, true);
          _this.props.actions.openAlertDialog("You must be logged in to save progress");
          return;
        }
        props.actions.goToNext();
      },
      handleGoToPrevious() {
        if (!_this.props.loginReducer.loggedInUser) {
          _this.props.actions.selectModalTab(1, 1, true);
          _this.props.actions.openAlertDialog("You must be logged in to save progress");
          return;
        }
        props.actions.goToPrevious();
      },
      handleOpenDialog(goToNextOrPrevious) {
        _this.setState({goToNextOrPrevious})
        _this.setState({dialogModalVisibility: true});
      },
      handleCloseDialog() {
        _this.setState({dialogModalVisibility: false});
      },
      skipToNext() {
        _this.setState({dialogModalVisibility: false});
        props.actions.goToNext();
      },
      skipToPrevious() {
        _this.setState({dialogModalVisibility: false});
        props.actions.goToPrevious();
      },
      changeSelectionsInLocalState(selections) {
        _this.setState({ selections });
      },
      changeMode(mode) {
        _this.setState({
          mode: mode,
          selections: _this.props.selectionsReducer.selections
        });
      },
      handleComment(e) {
        const comment = e.target.value;
        _this.setState({
          comment: comment
        });
      },
      checkComment(e) {
        const newcomment = e.target.value || "";
        const oldcomment = _this.props.commentsReducer.text || "";
        _this.setState({
          commentChanged: newcomment !== oldcomment
        })
      },
      cancelComment(e) {
        _this.setState({
          mode: 'default',
          selections: _this.props.selectionsReducer.selections,
          comment: undefined,
          commentChanged: false
        });
      },
      saveComment() {
        if (!_this.props.loginReducer.loggedInUser) {
          _this.props.actions.selectModalTab(1, 1, true);
          _this.props.actions.openAlertDialog("You must be logged in to leave a comment", 5);
          return;
        }
        _this.props.actions.addComment(_this.state.comment, _this.props.loginReducer.userdata.username);
        _this.setState({
          mode: 'default',
          selections: _this.props.selectionsReducer.selections,
          comment: undefined,
          commentChanged: false
        });
      },
      handleTagsCheckbox(tag, e) {
        let newState = _this.state;
        if (newState.tags === undefined) newState.tags = [];
        if (!newState.tags.includes(tag)) {
          newState.tags.push(tag);
        } else {
          newState.tags = newState.tags.filter( _tag => _tag !== tag);
        }
        _this.setState(newState);
      },
      handleEditVerse(e) {
        const verseText = e.target.value;
        _this.setState({
          verseText: verseText
        });
      },
      checkVerse(e) {
        let {chapter, verse} = _this.props.contextIdReducer.contextId.reference;
        const newverse = e.target.value || "";
        const oldverse = _this.props.resourcesReducer.bibles.targetLanguage[chapter][verse] || "";
        if (newverse === oldverse) {
          _this.setState({
            verseChanged: false,
            tags: []
          });
        } else {
          _this.setState({
            verseChanged: true
          });
        }
      },
      cancelEditVerse() {
        _this.setState({
          mode: 'default',
          selections: _this.props.selectionsReducer.selections,
          verseText: undefined,
          verseChanged: false,
          tags: []
        });
      },
      saveEditVerse() {
        let {loginReducer, actions, contextIdReducer, resourcesReducer} = _this.props;
        let {chapter, verse} = contextIdReducer.contextId.reference;
        let before = resourcesReducer.bibles.targetLanguage[chapter][verse];
        let username = loginReducer.userdata.username;
        // verseText state is undefined if no changes are made in the text box.
        if (!loginReducer.loggedInUser) {
          _this.props.actions.selectModalTab(1, 1, true);
          _this.props.actions.openAlertDialog("You must be logged in to edit a verse");
          return;
        }

        const save = () => {
          actions.addVerseEdit(before, _this.state.verseText, _this.state.tags, username);
          _this.setState({
            mode: 'default',
            selections: _this.props.selectionsReducer.selections,
            verseText: undefined,
            verseChanged: false,
            tags: []
          });
        }
        if (_this.state.verseText) {  // if verseText === "" is false
          save();
        } else {
          // alert the user if the text is blank
          let message = 'You are saving a blank verse. Please confirm.';
          _this.props.actions.openOptionDialog(message, (option)=> {
            if (option !== "Cancel") save();
            _this.props.actions.closeAlertDialog();
          }, "Save Blank Verse", "Cancel");
        }
      },
      validateSelections(verseText) {
        _this.props.actions.validateSelections(verseText)
      },
      toggleReminder() {
        _this.props.actions.toggleReminder(_this.props.loginReducer.userdata.username)
      },
      openAlertDialog(message) {
        _this.props.actions.openAlertDialog(message)
      },
      selectModalTab(tab, section, vis) {
        _this.props.actions.selectModalTab(tab, section, vis);
      }
    }
  }

  componentWillMount() {
    let selections = Array.from(this.props.selectionsReducer.selections);
    this.setState({selections});
  }

  componentWillReceiveProps(nextProps) {
    if (nextProps.contextIdReducer.contextId != this.props.contextIdReducer.contextId) {
      let selections = Array.from(nextProps.selectionsReducer.selections);
      const mode = nextProps.selectionsReducer.selections.length > 0 ? 'default' : 'select';
      this.setState({
        mode: mode,
        comments: undefined,
        verseText: undefined,
        selections,
        tags: []
      });
    }
  }

  cancelSelection() {
    this.actions.changeSelectionsInLocalState(this.props.selectionsReducer.selections);
    this.actions.changeMode('default');
  }

  clearSelection() {
    this.setState({
      selections: []
    });
  }

  saveSelection() {
    let verseText = this.verseText();
    // optimize the selections to address potential issues and save
    let selections = optimizeSelections(verseText, this.state.selections);
    this.props.actions.changeSelections(selections, this.props.loginReducer.userdata.username);
    this.actions.changeMode('default');
  }

  verseText() {
    const { chapter, verse, bookId } = this.props.contextIdReducer.contextId.reference;
    const bookAbbr = this.props.projectDetailsReducer.manifest.project.id;
    const { targetLanguage } = this.props.resourcesReducer.bibles;
    let verseText = "";
    if (targetLanguage && targetLanguage[chapter] && bookId == bookAbbr) {
      verseText = targetLanguage[chapter][verse] || "";
      // normalize whitespace in case selection has contiguous whitespace _this isn't captured
      verseText = normalizeString(verseText);
    }
    return verseText;
  }

  render() {
    let verseText = usfmjs.removeMarker(this.verseText())
    return (
      <MuiThemeProvider>
        <View {...this.props} actions={this.actions}
          cancelSelection={this.cancelSelection.bind(this)}
          clearSelection={this.clearSelection.bind(this)}
          saveSelection={this.saveSelection.bind(this)}
          mode={this.state.mode}
          comment={this.props.commentsReducer.text}
          commentChanged={this.state.commentChanged}
          verseText={verseText}
          verseChanged={this.state.verseChanged}
          selections={this.state.selections}
          tags={this.state.tags}
          dialogModalVisibility={this.state.dialogModalVisibility}
          goToNextOrPrevious={this.state.goToNextOrPrevious}
        />
      </MuiThemeProvider>
    );
  }
}

export default VerseCheck;
<commit_msg>verse check usfm 3 compatibility<commit_after>  /**
  * @author Christopher Klpap
  * @description This component displays the Verse so selection, edit and comments can be made
  */
import React from 'react';
import usfmjs from 'usfm-js';
import View from './components/View';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import {optimizeSelections, normalizeString} from './utils/selectionHelpers';

class VerseCheck extends React.Component {
  constructor(props) {
    super(props);
    const mode = props.selectionsReducer.selections.length > 0 ? 'default' : 'select';
    this.state = {
      mode: mode,
      comment: undefined,
      commentChanged: false,
      verseText: undefined,
      verseChanged: false,
      selections: [],
      tags: [],
      dialogModalVisibility: false,
      goToNextOrPrevious: null
    };

    let _this = this;

    this.tagList = [
      ["spelling", "Spelling"],
      ["punctuation", "Punctuation"],
      ["grammar", "Grammar"],
      ["meaning", "Meaning"],
      ["wordChoice", "Word Choice"],
      ["other", "Other"]
    ]

    this.actions = {
      handleGoToNext() {
        if (!_this.props.loginReducer.loggedInUser) {
          _this.props.actions.selectModalTab(1, 1, true);
          _this.props.actions.openAlertDialog("You must be logged in to save progress");
          return;
        }
        props.actions.goToNext();
      },
      handleGoToPrevious() {
        if (!_this.props.loginReducer.loggedInUser) {
          _this.props.actions.selectModalTab(1, 1, true);
          _this.props.actions.openAlertDialog("You must be logged in to save progress");
          return;
        }
        props.actions.goToPrevious();
      },
      handleOpenDialog(goToNextOrPrevious) {
        _this.setState({goToNextOrPrevious})
        _this.setState({dialogModalVisibility: true});
      },
      handleCloseDialog() {
        _this.setState({dialogModalVisibility: false});
      },
      skipToNext() {
        _this.setState({dialogModalVisibility: false});
        props.actions.goToNext();
      },
      skipToPrevious() {
        _this.setState({dialogModalVisibility: false});
        props.actions.goToPrevious();
      },
      changeSelectionsInLocalState(selections) {
        _this.setState({ selections });
      },
      changeMode(mode) {
        _this.setState({
          mode: mode,
          selections: _this.props.selectionsReducer.selections
        });
      },
      handleComment(e) {
        const comment = e.target.value;
        _this.setState({
          comment: comment
        });
      },
      checkComment(e) {
        const newcomment = e.target.value || "";
        const oldcomment = _this.props.commentsReducer.text || "";
        _this.setState({
          commentChanged: newcomment !== oldcomment
        })
      },
      cancelComment(e) {
        _this.setState({
          mode: 'default',
          selections: _this.props.selectionsReducer.selections,
          comment: undefined,
          commentChanged: false
        });
      },
      saveComment() {
        if (!_this.props.loginReducer.loggedInUser) {
          _this.props.actions.selectModalTab(1, 1, true);
          _this.props.actions.openAlertDialog("You must be logged in to leave a comment", 5);
          return;
        }
        _this.props.actions.addComment(_this.state.comment, _this.props.loginReducer.userdata.username);
        _this.setState({
          mode: 'default',
          selections: _this.props.selectionsReducer.selections,
          comment: undefined,
          commentChanged: false
        });
      },
      handleTagsCheckbox(tag, e) {
        let newState = _this.state;
        if (newState.tags === undefined) newState.tags = [];
        if (!newState.tags.includes(tag)) {
          newState.tags.push(tag);
        } else {
          newState.tags = newState.tags.filter( _tag => _tag !== tag);
        }
        _this.setState(newState);
      },
      handleEditVerse(e) {
        const verseText = e.target.value;
        _this.setState({
          verseText: verseText
        });
      },
      checkVerse(e) {
        let {chapter, verse} = _this.props.contextIdReducer.contextId.reference;
        const newverse = e.target.value || "";
        const oldverse = _this.props.resourcesReducer.bibles.targetLanguage[chapter][verse] || "";
        if (newverse === oldverse) {
          _this.setState({
            verseChanged: false,
            tags: []
          });
        } else {
          _this.setState({
            verseChanged: true
          });
        }
      },
      cancelEditVerse() {
        _this.setState({
          mode: 'default',
          selections: _this.props.selectionsReducer.selections,
          verseText: undefined,
          verseChanged: false,
          tags: []
        });
      },
      saveEditVerse() {
        let {loginReducer, actions, contextIdReducer, resourcesReducer} = _this.props;
        let {chapter, verse} = contextIdReducer.contextId.reference;
        let before = resourcesReducer.bibles.targetLanguage[chapter][verse];
        let username = loginReducer.userdata.username;
        // verseText state is undefined if no changes are made in the text box.
        if (!loginReducer.loggedInUser) {
          _this.props.actions.selectModalTab(1, 1, true);
          _this.props.actions.openAlertDialog("You must be logged in to edit a verse");
          return;
        }

        const save = () => {
          actions.addVerseEdit(before, _this.state.verseText, _this.state.tags, username);
          _this.setState({
            mode: 'default',
            selections: _this.props.selectionsReducer.selections,
            verseText: undefined,
            verseChanged: false,
            tags: []
          });
        }
        if (_this.state.verseText) {  // if verseText === "" is false
          save();
        } else {
          // alert the user if the text is blank
          let message = 'You are saving a blank verse. Please confirm.';
          _this.props.actions.openOptionDialog(message, (option)=> {
            if (option !== "Cancel") save();
            _this.props.actions.closeAlertDialog();
          }, "Save Blank Verse", "Cancel");
        }
      },
      validateSelections(verseText) {
        _this.props.actions.validateSelections(verseText)
      },
      toggleReminder() {
        _this.props.actions.toggleReminder(_this.props.loginReducer.userdata.username)
      },
      openAlertDialog(message) {
        _this.props.actions.openAlertDialog(message)
      },
      selectModalTab(tab, section, vis) {
        _this.props.actions.selectModalTab(tab, section, vis);
      }
    }
  }

  componentWillMount() {
    let selections = Array.from(this.props.selectionsReducer.selections);
    this.setState({selections});
  }

  componentWillReceiveProps(nextProps) {
    if (nextProps.contextIdReducer.contextId != this.props.contextIdReducer.contextId) {
      let selections = Array.from(nextProps.selectionsReducer.selections);
      const mode = nextProps.selectionsReducer.selections.length > 0 ? 'default' : 'select';
      this.setState({
        mode: mode,
        comments: undefined,
        verseText: undefined,
        selections,
        tags: []
      });
    }
  }

  cancelSelection() {
    this.actions.changeSelectionsInLocalState(this.props.selectionsReducer.selections);
    this.actions.changeMode('default');
  }

  clearSelection() {
    this.setState({
      selections: []
    });
  }

  saveSelection() {
    let verseText = this.verseText();
    // optimize the selections to address potential issues and save
    let selections = optimizeSelections(verseText, this.state.selections);
    this.props.actions.changeSelections(selections, this.props.loginReducer.userdata.username);
    this.actions.changeMode('default');
  }

  verseText() {
    const { chapter, verse, bookId } = this.props.contextIdReducer.contextId.reference;
    const bookAbbr = this.props.projectDetailsReducer.manifest.project.id;
    const { targetLanguage } = this.props.resourcesReducer.bibles;
    let verseText = "";
    if (targetLanguage && targetLanguage[chapter] && bookId == bookAbbr) {
      verseText = targetLanguage[chapter][verse] || "";
      if (Array.isArray(verseText)) verseText = verseText[0];
      // normalize whitespace in case selection has contiguous whitespace _this isn't captured
      verseText = normalizeString(verseText);
    }
    return verseText;
  }

  render() {
    let verseText = usfmjs.removeMarker(this.verseText())
    return (
      <MuiThemeProvider>
        <View {...this.props} actions={this.actions}
          cancelSelection={this.cancelSelection.bind(this)}
          clearSelection={this.clearSelection.bind(this)}
          saveSelection={this.saveSelection.bind(this)}
          mode={this.state.mode}
          comment={this.props.commentsReducer.text}
          commentChanged={this.state.commentChanged}
          verseText={verseText}
          verseChanged={this.state.verseChanged}
          selections={this.state.selections}
          tags={this.state.tags}
          dialogModalVisibility={this.state.dialogModalVisibility}
          goToNextOrPrevious={this.state.goToNextOrPrevious}
        />
      </MuiThemeProvider>
    );
  }
}

export default VerseCheck;
<|endoftext|>


<commit_before>// cloudq run queue
require('date-utils');

var flatiron = require('flatiron'),
  request = require('request'),
  es = require('event-stream'),
  parseRows = require('JSONStream').parse(['rows', true, 'value']),
  app = flatiron.app;

//var cloudq = process.env.COUCH;
var cloudq = process.env.COUCH || 'http://localhost:5984/cloudq';

// get cloudq job
app.router.get('/:queue', function(queue) {
  var self = this;
  console.log(queue)
  // get job from queue
  request(cloudq + '/_design/queued/_view/name?limit=1', { json: { key: queue } }, function(e,r,b){
    // if found
    if(b && b.rows && b.rows.length === 1) {
      var job = b.rows[0].value.job;
      job.id = b.rows[0].id;
      reserveJob(self.res, job);
    } else {
      // return queue empty
      empty(self.res);
    }
  });
});

// post cloudq job
app.router.post('/:queue', function(queue){
  var self = this;
  var job = function(data, cb) {
    var doc = JSON.parse(data);
    doc = jobify(doc, queue);
    cb(null, JSON.stringify(doc));
  }

  // validate job - should have job node...
  if(this.req.body.job) {
    es.pipeline(
      this.req,
      es.map(job),
      request.post(cloudq, { json: true }),
      this.res
    );
  } else {
    renderErr(this.res, {error: 'Job Object is required!'});
  }

});

// complete cloudq job
app.router.delete('/:queue/:id', function(queue, id){
  var self = this;
  es.pipeline(
    this.req,
    request.put(cloudq + '/_design/complete/_update/id/' + id, {json: true }),
    es.mapSync(function(data){ success(self.res); })
  );
});

function success(res) {
  res.writeHead(200, {'Content-Type': 'application/json'});
  res.end(JSON.stringify({status: 'success'}));
}

function empty(res) {
  res.writeHead(200, {'Content-Type': 'application/json'});
  res.end(JSON.stringify({status: 'empty'}));
}

function renderJob(res, job) {
  res.writeHead(200, {'Content-Type': 'application/json'});
  res.end(JSON.stringify(job));
}

function renderErr(res, err) {
  res.writeHead(400, {'Content-Type': 'application/json'});
  res.end(JSON.stringify(err));
}

function reserveJob(res, job) {
  // set it to reserved state
  request.put(cloudq + '/_design/dequeue/_update/id/' + job.id, {json: true}, function(err){
    // return job if successful
    if(!err){
      renderJob(res, job);
    } else {
      renderErr(res, {error: 'Unable to dequeue - ' + id});
    }
  });
}

function jobify(job, queue) {
  job.queue = queue;
  job.queue_state = 'queued';
  if(!job.priority) { job.priority = 1 };
  job.expires_in = (new Date()).addDays(1);
  return job;
}
<commit_msg>fixed tests<commit_after>// cloudq run queue
require('date-utils');

var flatiron = require('flatiron'),
  request = require('request'),
  es = require('event-stream'),
  parseRows = require('JSONStream').parse(['rows', true, 'value']),
  app = flatiron.app;

//var cloudq = process.env.COUCH;
var cloudq = process.env.COUCH || 'http://localhost:5984/cloudq';

// get cloudq job
app.router.get('/:queue', function(queue) {
  var self = this;
  // get job from queue
  request(cloudq + '/_design/queued/_view/name?limit=1', { json: { key: queue } }, function(e,r,b){
    // if found
    if(b && b.rows && b.rows.length === 1) {
      var job = b.rows[0].value.job;
      job.id = b.rows[0].id;
      reserveJob(self.res, job);
    } else {
      // return queue empty
      empty(self.res);
    }
  });
});

// post cloudq job
app.router.post('/:queue', function(queue){
  var self = this;
  var job = function(data, cb) {
    var doc = JSON.parse(data);
    doc = jobify(doc, queue);
    cb(null, JSON.stringify(doc));
  }

  // validate job - should have job node...
  if(this.req.body.job) {
    es.pipeline(
      this.req,
      es.map(job),
      request.post(cloudq, { json: true }),
      this.res
    );
  } else {
    renderErr(this.res, {error: 'Job Object is required!'});
  }

});

// complete cloudq job
app.router.delete('/:queue/:id', function(queue, id){
  var self = this;
  es.pipeline(
    this.req,
    request.put(cloudq + '/_design/complete/_update/id/' + id, {json: true }),
    es.mapSync(function(data){ success(self.res); })
  );
});

function success(res) {
  res.writeHead(200, {'Content-Type': 'application/json'});
  res.end(JSON.stringify({status: 'success'}));
}

function empty(res) {
  res.writeHead(200, {'Content-Type': 'application/json'});
  res.end(JSON.stringify({status: 'empty'}));
}

function renderJob(res, job) {
  res.writeHead(200, {'Content-Type': 'application/json'});
  res.end(JSON.stringify(job));
}

function renderErr(res, err) {
  res.writeHead(400, {'Content-Type': 'application/json'});
  res.end(JSON.stringify(err));
}

function reserveJob(res, job) {
  // set it to reserved state
  request.put(cloudq + '/_design/dequeue/_update/id/' + job.id, {json: true}, function(err){
    // return job if successful
    if(!err){
      renderJob(res, job);
    } else {
      renderErr(res, {error: 'Unable to dequeue - ' + id});
    }
  });
}

function jobify(job, queue) {
  job.queue = queue;
  job.queue_state = 'queued';
  if(!job.priority) { job.priority = 1 };
  job.expires_in = (new Date()).addDays(1);
  return job;
}
<|endoftext|>


<commit_before>'use strict';

module.exports = {
  env: {
    es6: true,
    node: true,
  },
  parserOptions: {
    ecmaVersion: 2017,
    sourceType: 'module',
  },
  rules: {
    // possible errors
    'no-cond-assign': ['error', 'always'],
    'no-console': 'warn',
    'no-constant-condition': 'error',
    'no-control-regex': 'error',
    'no-debugger': 'warn',
    'no-dupe-args': 'error',
    'no-dupe-keys': 'error',
    'no-duplicate-case': 'error',
    'no-empty-character-class': 'error',
    'no-empty': ['error', {allowEmptyCatch: true}],
    'no-ex-assign': 'error',
    'no-extra-boolean-cast': 'error',
    // issue: https://github.com/eslint/eslint/issues/3107
    'no-extra-parens': ['error', 'functions'],
    'no-extra-semi': 'error',
    'no-func-assign': 'error',
    'no-inner-declarations': 'error',
    'no-invalid-regexp': 'error',
    'no-irregular-whitespace': 'error',
    'no-obj-calls': 'error',
    'no-prototype-builtins': 'error',
    'no-regex-spaces': 'error',
    'no-sparse-arrays': 'error',
    'no-template-curly-in-string': 'error',
    'no-unexpected-multiline': 'error',
    'no-unreachable': 'error',
    'no-unsafe-finally': 'error',
    'no-unsafe-negation': 'error',
    'use-isnan': 'error',
    'valid-jsdoc': 'off',
    'valid-typeof': ['error', {requireStringLiterals: true}],

    // best practices
    'accessor-pairs': 'error',
    'array-callback-return': 'error',
    'block-scoped-var': 'error', // rely on `no-var` rule
    'class-methods-use-this': 'off',
    'complexity': ['warn', 11],
    'consistent-return': 'error',
    'curly': 'error',
    'default-case': 'error',
    'dot-location': ['error', 'property'],
    'dot-notation': 'error',
    'eqeqeq': 'error',
    'guard-for-in': 'error', // rely on `no-restricted-syntax` rule
    'no-alert': 'warn',
    'no-caller': 'error',
    'no-case-declarations': 'error',
    'no-div-regex': 'off',
    'no-else-return': 'error',
    'no-empty-function': 'error',
    'no-empty-pattern': 'error',
    'no-eq-null': 'error', // rely on `eqeqeq` rule
    'no-eval': 'error',
    'no-extend-native': 'error',
    'no-extra-bind': 'error',
    'no-extra-label': 'error',
    'no-fallthrough': 'error',
    'no-floating-decimal': 'error',
    'no-global-assign': 'error',
    'no-implicit-coercion': ['error', {boolean: false}],
    'no-implicit-globals': 'error',
    'no-implied-eval': 'error',
    // TODO: Enable when issue has been fixed.
    // issue: https://github.com/babel/eslint-plugin-babel/issues/12
    // 'no-invalid-this': 'error',
    'no-iterator': 'error',
    'no-labels': 'error',
    'no-lone-blocks': 'error',
    'no-loop-func': 'error',
    'no-magic-numbers': 'off',
    'no-multi-spaces': 'error',
    'no-multi-str': 'error',
    'no-new-func': 'error',
    'no-new-wrappers': 'error',
    'no-new': 'error',
    'no-octal-escape': 'error',
    'no-octal': 'error',
    'no-param-reassign': ['error', {props: true}],
    'no-proto': 'error',
    'no-redeclare': 'error',
    'no-return-assign': ['error', 'always'],
    'no-script-url': 'off',
    'no-self-assign': ['error', {props: true}],
    'no-self-compare': 'error',
    'no-sequences': 'error',
    'no-throw-literal': 'error',
    'no-unmodified-loop-condition': 'error',
    'no-unused-expressions': 'error',
    'no-unused-labels': 'error',
    'no-useless-call': 'error',
    'no-useless-concat': 'error',
    'no-useless-escape': 'error',
    'no-void': 'error',
    'no-warning-comments': 'warn',
    'no-with': 'error',
    'radix': ['error', 'as-needed'],
    'vars-on-top': 'error', // rely on `one-var` rule
    'wrap-iife': ['error', 'inside'],
    'yoda': 'error',

    // strict mode
    'strict': 'off',

    // variables
    'init-declarations': 'error',
    'no-catch-shadow': 'off',
    'no-delete-var': 'error',
    'no-label-var': 'error',
    'no-restricted-globals': ['error', 'isNaN', 'parseInt', 'parseFloat'], // use `Number` property functions instead
    'no-shadow-restricted-names': 'error',
    'no-shadow': 'error',
    'no-undef-init': 'error',
    'no-undef': 'error',
    'no-undefined': 'off',
    'no-unused-vars': 'error',
    'no-use-before-define': ['error', 'nofunc'],

    // node.js and commonjs
    'callback-return': 'off',
    'global-require': 'off',
    'handle-callback-err': 'error',
    'no-mixed-requires': 'error', // rely on `one-var` rule
    'no-new-require': 'error',
    'no-path-concat': 'error',
    'no-process-env': 'off',
    'no-process-exit': 'off',
    'no-restricted-modules': 'off',
    'no-restricted-properties': [
      'error',
      {
        object: 'Object',
        property: 'assign',
        message: 'Use object spread operator `{...obj}` instead.',
      },
    ],
    'no-sync': 'off',

    // stylistic issues
    'array-bracket-spacing': 'error',
    'block-spacing': 'error',
    'brace-style': ['error', '1tbs', {allowSingleLine: true}],
    'camelcase': ['error', {properties: 'never'}],
    'comma-dangle': ['error', 'always-multiline'],
    'comma-spacing': 'error',
    'comma-style': ['error', 'last'],
    'computed-property-spacing': 'error',
    'consistent-this': 'off',
    'eol-last': 'error',
    'func-call-spacing': 'error',
    'func-names': ['error', 'never'],
    'func-style': ['error', 'declaration', {allowArrowFunctions: true}],
    'id-blacklist': 'off',
    'id-length': 'off',
    'id-match': 'off',
    'indent': ['error', 2, {SwitchCase: 1}],
    'jsx-quotes': 'off',
    'key-spacing': 'error',
    'line-comment-position': 'off',
    'keyword-spacing': 'error',
    'linebreak-style': 'error',
    'lines-around-comment': 'off',
    'lines-around-directive': 'error',
    'max-depth': 'warn',
    'max-len': 'off',
    'max-lines': 'off',
    'max-nested-callbacks': ['warn', 4],
    'max-params': ['warn', 5],
    'max-statements-per-line': 'warn',
    'max-statements': 'off',
    'multiline-ternary': 'off',
    'new-cap': 'error',
    'new-parens': 'error',
    'newline-after-var': 'error',
    'newline-before-return': 'error',
    'newline-per-chained-call': 'error',
    'no-array-constructor': 'error',
    'no-bitwise': 'off',
    'no-continue': 'error',
    'no-inline-comments': 'off',
    'no-lonely-if': 'error',
    'no-mixed-operators': 'error',
    'no-mixed-spaces-and-tabs': 'error',
    'no-multiple-empty-lines': ['error', {max: 1, maxBOF: 0}],
    'no-negated-condition': 'error',
    // IMO, there is no better alternatives
    // http://stackoverflow.com/questions/32289340/alternative-to-nested-ternary-operator-in-js
    'no-nested-ternary': 'off',
    'no-new-object': 'error',
    'no-plusplus': 'off',
    // use for-of with Object.keys() instead of for-in
    // https://hacks.mozilla.org/2015/04/es6-in-depth-iterators-and-the-for-of-loop/
    'no-restricted-syntax': ['error', 'ForInStatement', 'LabeledStatement', 'WithStatement'],
    'no-tabs': 'error',
    'no-ternary': 'off',
    'no-trailing-spaces': ['error', {skipBlankLines: true}],
    'no-underscore-dangle': ['error', {allowAfterThis: true, allowAfterSuper: true}],
    'no-unneeded-ternary': ['error', {defaultAssignment: false}],
    'no-whitespace-before-property': 'error',
    // TODO: Enable when issue has been fixed.
    // issue: https://github.com/eslint/eslint/issues/6488
    // 'object-curly-newline': ['error', {multiline: true, minProperties: 4}],
    'object-curly-spacing': 'error',
    'object-property-newline': ['error', {allowMultiplePropertiesPerLine: true}],
    'one-var-declaration-per-line': 'error', // rely on `one-var` rule
    'one-var': ['error', 'never'],
    'operator-assignment': ['error', 'always'],
    'operator-linebreak': 'error',
    'padded-blocks': ['error', 'never'],
    'quote-props': ['error', 'as-needed'],
    'quotes': ['error', 'single'],
    'require-jsdoc': 'off',
    'semi-spacing': 'error',
    'semi': 'error',
    'sort-keys': 'off',
    'sort-vars': 'off',
    'space-before-blocks': 'error',
    'space-before-function-paren': ['error', {anonymous: 'always', named: 'never'}],
    'space-in-parens': 'error',
    'space-infix-ops': 'error',
    'space-unary-ops': 'error',
    'spaced-comment': ['error', 'always', {block: {balanced: true}}],
    'unicode-bom': 'error',
    'wrap-regex': 'off',

    // ecmascript 6 rules
    'arrow-body-style': ['error', 'as-needed'],
    'arrow-parens': 'error',
    'arrow-spacing': 'error',
    'constructor-super': 'error',
    'generator-star-spacing': 'error',
    'no-class-assign': 'error',
    'no-confusing-arrow': 'off',
    'no-const-assign': 'error',
    'no-dupe-class-members': 'error',
    'no-duplicate-imports': ['error', {includeExports: true}],
    'no-new-symbol': 'error',
    'no-restricted-imports': 'off',
    'no-this-before-super': 'error',
    'no-useless-computed-key': 'error',
    'no-useless-constructor': 'error',
    'no-useless-rename': 'error',
    'no-var': 'error',
    'object-shorthand': 'error',
    'prefer-arrow-callback': 'error',
    'prefer-const': 'error',
    'prefer-numeric-literals': 'error',
    'prefer-reflect': 'error',
    'prefer-rest-params': 'error',
    'prefer-spread': 'error',
    'prefer-template': 'error',
    'require-yield': 'error',
    'rest-spread-spacing': 'error',
    'sort-imports': 'off',
    'symbol-description': 'off',
    'template-curly-spacing': 'error',
    'yield-star-spacing': 'error',
  },
};
<commit_msg>Enable `strict` rule<commit_after>'use strict';

module.exports = {
  env: {
    es6: true,
    node: true,
  },
  parserOptions: {
    ecmaVersion: 2017,
    sourceType: 'module',
  },
  rules: {
    // possible errors
    'no-cond-assign': ['error', 'always'],
    'no-console': 'warn',
    'no-constant-condition': 'error',
    'no-control-regex': 'error',
    'no-debugger': 'warn',
    'no-dupe-args': 'error',
    'no-dupe-keys': 'error',
    'no-duplicate-case': 'error',
    'no-empty-character-class': 'error',
    'no-empty': ['error', {allowEmptyCatch: true}],
    'no-ex-assign': 'error',
    'no-extra-boolean-cast': 'error',
    // issue: https://github.com/eslint/eslint/issues/3107
    'no-extra-parens': ['error', 'functions'],
    'no-extra-semi': 'error',
    'no-func-assign': 'error',
    'no-inner-declarations': 'error',
    'no-invalid-regexp': 'error',
    'no-irregular-whitespace': 'error',
    'no-obj-calls': 'error',
    'no-prototype-builtins': 'error',
    'no-regex-spaces': 'error',
    'no-sparse-arrays': 'error',
    'no-template-curly-in-string': 'error',
    'no-unexpected-multiline': 'error',
    'no-unreachable': 'error',
    'no-unsafe-finally': 'error',
    'no-unsafe-negation': 'error',
    'use-isnan': 'error',
    'valid-jsdoc': 'off',
    'valid-typeof': ['error', {requireStringLiterals: true}],

    // best practices
    'accessor-pairs': 'error',
    'array-callback-return': 'error',
    'block-scoped-var': 'error', // rely on `no-var` rule
    'class-methods-use-this': 'off',
    'complexity': ['warn', 11],
    'consistent-return': 'error',
    'curly': 'error',
    'default-case': 'error',
    'dot-location': ['error', 'property'],
    'dot-notation': 'error',
    'eqeqeq': 'error',
    'guard-for-in': 'error', // rely on `no-restricted-syntax` rule
    'no-alert': 'warn',
    'no-caller': 'error',
    'no-case-declarations': 'error',
    'no-div-regex': 'off',
    'no-else-return': 'error',
    'no-empty-function': 'error',
    'no-empty-pattern': 'error',
    'no-eq-null': 'error', // rely on `eqeqeq` rule
    'no-eval': 'error',
    'no-extend-native': 'error',
    'no-extra-bind': 'error',
    'no-extra-label': 'error',
    'no-fallthrough': 'error',
    'no-floating-decimal': 'error',
    'no-global-assign': 'error',
    'no-implicit-coercion': ['error', {boolean: false}],
    'no-implicit-globals': 'error',
    'no-implied-eval': 'error',
    // TODO: Enable when issue has been fixed.
    // issue: https://github.com/babel/eslint-plugin-babel/issues/12
    // 'no-invalid-this': 'error',
    'no-iterator': 'error',
    'no-labels': 'error',
    'no-lone-blocks': 'error',
    'no-loop-func': 'error',
    'no-magic-numbers': 'off',
    'no-multi-spaces': 'error',
    'no-multi-str': 'error',
    'no-new-func': 'error',
    'no-new-wrappers': 'error',
    'no-new': 'error',
    'no-octal-escape': 'error',
    'no-octal': 'error',
    'no-param-reassign': ['error', {props: true}],
    'no-proto': 'error',
    'no-redeclare': 'error',
    'no-return-assign': ['error', 'always'],
    'no-script-url': 'off',
    'no-self-assign': ['error', {props: true}],
    'no-self-compare': 'error',
    'no-sequences': 'error',
    'no-throw-literal': 'error',
    'no-unmodified-loop-condition': 'error',
    'no-unused-expressions': 'error',
    'no-unused-labels': 'error',
    'no-useless-call': 'error',
    'no-useless-concat': 'error',
    'no-useless-escape': 'error',
    'no-void': 'error',
    'no-warning-comments': 'warn',
    'no-with': 'error',
    'radix': ['error', 'as-needed'],
    'vars-on-top': 'error', // rely on `one-var` rule
    'wrap-iife': ['error', 'inside'],
    'yoda': 'error',

    // strict mode
    'strict': 'error',

    // variables
    'init-declarations': 'error',
    'no-catch-shadow': 'off',
    'no-delete-var': 'error',
    'no-label-var': 'error',
    'no-restricted-globals': ['error', 'isNaN', 'parseInt', 'parseFloat'], // use `Number` property functions instead
    'no-shadow-restricted-names': 'error',
    'no-shadow': 'error',
    'no-undef-init': 'error',
    'no-undef': 'error',
    'no-undefined': 'off',
    'no-unused-vars': 'error',
    'no-use-before-define': ['error', 'nofunc'],

    // node.js and commonjs
    'callback-return': 'off',
    'global-require': 'off',
    'handle-callback-err': 'error',
    'no-mixed-requires': 'error', // rely on `one-var` rule
    'no-new-require': 'error',
    'no-path-concat': 'error',
    'no-process-env': 'off',
    'no-process-exit': 'off',
    'no-restricted-modules': 'off',
    'no-restricted-properties': [
      'error',
      {
        object: 'Object',
        property: 'assign',
        message: 'Use object spread operator `{...obj}` instead.',
      },
    ],
    'no-sync': 'off',

    // stylistic issues
    'array-bracket-spacing': 'error',
    'block-spacing': 'error',
    'brace-style': ['error', '1tbs', {allowSingleLine: true}],
    'camelcase': ['error', {properties: 'never'}],
    'comma-dangle': ['error', 'always-multiline'],
    'comma-spacing': 'error',
    'comma-style': ['error', 'last'],
    'computed-property-spacing': 'error',
    'consistent-this': 'off',
    'eol-last': 'error',
    'func-call-spacing': 'error',
    'func-names': ['error', 'never'],
    'func-style': ['error', 'declaration', {allowArrowFunctions: true}],
    'id-blacklist': 'off',
    'id-length': 'off',
    'id-match': 'off',
    'indent': ['error', 2, {SwitchCase: 1}],
    'jsx-quotes': 'off',
    'key-spacing': 'error',
    'line-comment-position': 'off',
    'keyword-spacing': 'error',
    'linebreak-style': 'error',
    'lines-around-comment': 'off',
    'lines-around-directive': 'error',
    'max-depth': 'warn',
    'max-len': 'off',
    'max-lines': 'off',
    'max-nested-callbacks': ['warn', 4],
    'max-params': ['warn', 5],
    'max-statements-per-line': 'warn',
    'max-statements': 'off',
    'multiline-ternary': 'off',
    'new-cap': 'error',
    'new-parens': 'error',
    'newline-after-var': 'error',
    'newline-before-return': 'error',
    'newline-per-chained-call': 'error',
    'no-array-constructor': 'error',
    'no-bitwise': 'off',
    'no-continue': 'error',
    'no-inline-comments': 'off',
    'no-lonely-if': 'error',
    'no-mixed-operators': 'error',
    'no-mixed-spaces-and-tabs': 'error',
    'no-multiple-empty-lines': ['error', {max: 1, maxBOF: 0}],
    'no-negated-condition': 'error',
    // IMO, there is no better alternatives
    // http://stackoverflow.com/questions/32289340/alternative-to-nested-ternary-operator-in-js
    'no-nested-ternary': 'off',
    'no-new-object': 'error',
    'no-plusplus': 'off',
    // use for-of with Object.keys() instead of for-in
    // https://hacks.mozilla.org/2015/04/es6-in-depth-iterators-and-the-for-of-loop/
    'no-restricted-syntax': ['error', 'ForInStatement', 'LabeledStatement', 'WithStatement'],
    'no-tabs': 'error',
    'no-ternary': 'off',
    'no-trailing-spaces': ['error', {skipBlankLines: true}],
    'no-underscore-dangle': ['error', {allowAfterThis: true, allowAfterSuper: true}],
    'no-unneeded-ternary': ['error', {defaultAssignment: false}],
    'no-whitespace-before-property': 'error',
    // TODO: Enable when issue has been fixed.
    // issue: https://github.com/eslint/eslint/issues/6488
    // 'object-curly-newline': ['error', {multiline: true, minProperties: 4}],
    'object-curly-spacing': 'error',
    'object-property-newline': ['error', {allowMultiplePropertiesPerLine: true}],
    'one-var-declaration-per-line': 'error', // rely on `one-var` rule
    'one-var': ['error', 'never'],
    'operator-assignment': ['error', 'always'],
    'operator-linebreak': 'error',
    'padded-blocks': ['error', 'never'],
    'quote-props': ['error', 'as-needed'],
    'quotes': ['error', 'single'],
    'require-jsdoc': 'off',
    'semi-spacing': 'error',
    'semi': 'error',
    'sort-keys': 'off',
    'sort-vars': 'off',
    'space-before-blocks': 'error',
    'space-before-function-paren': ['error', {anonymous: 'always', named: 'never'}],
    'space-in-parens': 'error',
    'space-infix-ops': 'error',
    'space-unary-ops': 'error',
    'spaced-comment': ['error', 'always', {block: {balanced: true}}],
    'unicode-bom': 'error',
    'wrap-regex': 'off',

    // ecmascript 6 rules
    'arrow-body-style': ['error', 'as-needed'],
    'arrow-parens': 'error',
    'arrow-spacing': 'error',
    'constructor-super': 'error',
    'generator-star-spacing': 'error',
    'no-class-assign': 'error',
    'no-confusing-arrow': 'off',
    'no-const-assign': 'error',
    'no-dupe-class-members': 'error',
    'no-duplicate-imports': ['error', {includeExports: true}],
    'no-new-symbol': 'error',
    'no-restricted-imports': 'off',
    'no-this-before-super': 'error',
    'no-useless-computed-key': 'error',
    'no-useless-constructor': 'error',
    'no-useless-rename': 'error',
    'no-var': 'error',
    'object-shorthand': 'error',
    'prefer-arrow-callback': 'error',
    'prefer-const': 'error',
    'prefer-numeric-literals': 'error',
    'prefer-reflect': 'error',
    'prefer-rest-params': 'error',
    'prefer-spread': 'error',
    'prefer-template': 'error',
    'require-yield': 'error',
    'rest-spread-spacing': 'error',
    'sort-imports': 'off',
    'symbol-description': 'off',
    'template-curly-spacing': 'error',
    'yield-star-spacing': 'error',
  },
};
<|endoftext|>


<commit_before>var util = require('util');
var events = require('events');
var config = require('config');
var redis = require('redis-scanstreams')(require('redis')).createClient(6379, config.get('redis.host'));
var tredis = require('then-redis').createClient(config.get('redis'));
var Promise = require('bluebird');
var models = require('./models');

function Playback() {

}

util.inherits(Playback, events.EventEmitter);

Playback.prototype.state = function (deviceId) {
  return new Promise(function(resolve, reject) {
    Promise.props({
      Connected: tredis.exists('musichub.device.' + deviceId + '.connection'),
      Current: tredis.get('musichub.device.' + deviceId + '.current'),
      Duration: tredis.get('musichub.device.' + deviceId + '.duration'),
      Playing: tredis.get('musichub.device.' + deviceId + '.playing'),
      Paused: tredis.get('musichub.device.' + deviceId + '.paused'),
      FromTime: tredis.get('musichub.device.' + deviceId + '.fromtime'),
      Position: tredis.get('musichub.device.' + deviceId + '.position'),
      Queue: tredis.lrange('musichub.device.' + deviceId + '.queue', 0, -1)
    }).then(function(result) {
      resolve({
        Device: deviceId,
        Connected: Boolean(parseInt(result.Connected)),
        Current: parseInt(result.Current),
        Duration: parseInt(result.Duration),
        Playing: Boolean(parseInt(result.Playing)),
        Paused: Boolean(parseInt(result.Paused)),
        FromTime: parseInt(result.FromTime),
        Position: parseInt(result.Position),
        Queue: result.Queue.map(function(item) {
          return parseInt(item);
        })
      });
    });
  });
}

Playback.prototype.updateState = function (deviceId) {
  return new Promise(function(resolve, reject) {
    Promise.props({
      current: tredis.lpop('musichub.device.' + deviceId + '.queue'),
      currentDeviceTrack: tredis.lpop('musichub.device.' + deviceId + '.queue.device'),
    }).then(function(props) {
      tredis.set('musichub.device.' + deviceId + '.current', props.current).then(function() {
        new models.DeviceTrack({
          DeviceId: deviceId,
          TrackId: props.current
        }).fetch().then(function(dt) {
          tredis.set('musichub.device.' + deviceId + '.duration', dt.get('TrackDuration')).then(function() {
            resolve(props.currentDeviceTrack);
          })
        });
      });
    })
  });
}

Playback.prototype.updatePosition = function (deviceId) {
  return new Promise(function(resolve, reject) {
    tredis.get('musichub.device.' + deviceId + '.position').then(function(currentPosition) {
      tredis.get('musichub.device.' + deviceId + '.fromtime').then(function(fromTime) {
        var newPosition = parseInt(currentPosition) + (Date.now() - parseInt(fromTime));
        tredis.set('musichub.device.' + deviceId + '.position', newPosition).then(function() {
          resolve();
        });
      });
    });
  });
}

Playback.prototype.addTrackToQueue = function (deviceId, trackId) {
  return new Promise(function(resolve, reject) {
    new models.DeviceTrack({
      DeviceId: deviceId,
      TrackId: trackId
    }).fetch().then(function(dt) {
      var deviceTrackId = dt.get('DeviceTrackId');
      Promise.all([
        tredis.rpush('musichub.device.' + deviceId + '.queue', trackId),
        tredis.rpush('musichub.device.' + deviceId + '.queue.device', deviceTrackId)
      ]).then(function() {
        resolve();
      })
    });
  });
}

Playback.prototype.requestNext = function (deviceId) {
  return new Promise(function(resolve, reject) {
    tredis.set('musichub.device.' + deviceId + '.playing', 0).then(function() {
    	this.emit('RequestNext', deviceId);
    	resolve();
    }.bind(this));
  }.bind(this));
}

Playback.prototype.play = function (deviceId) {
  return new Promise(function(resolve, reject) {
    Promise.all([
      tredis.set('musichub.device.' + deviceId + '.playing', 1),
      tredis.set('musichub.device.' + deviceId + '.paused', 0),
      tredis.set('musichub.device.' + deviceId + '.fromtime', Date.now())
    ]).then(function() {
    	this.emit('Play', deviceId);
    	resolve();
    }.bind(this));
  }.bind(this));
}

Playback.prototype.pause = function(deviceId) {
	return new Promise(function(resolve, reject) {
		Promise.all([
	    tredis.set('musichub.device.' + deviceId + '.paused', 1),
	    this.updatePosition(deviceId)
	  ]).then(function() {
	  	this.emit('Pause', deviceId);
	  	resolve();
	  }.bind(this));
	}.bind(this));
}

Playback.prototype.next = function (deviceId) {
  return new Promise(function(resolve, reject) {
    tredis.llen('musichub.device.' + deviceId + '.queue').then(function(queueLen) {
      if (parseInt(queueLen) > 0) {
        Promise.all([
          tredis.set('musichub.device.' + deviceId + '.playing', 1),
          tredis.set('musichub.device.' + deviceId + '.paused', 0)
        ]).then(function() {
          this.updateState(deviceId).then(function(currentDeviceTrack) {
            tredis.set('musichub.device.' + deviceId + '.fromtime', Date.now()).then(function() {
              tredis.set('musichub.device.' + deviceId + '.position', 0).then(function() {
              	this.emit('Next', deviceId, currentDeviceTrack);
                resolve(currentDeviceTrack);
              }.bind(this));
            }.bind(this));
          }.bind(this));
        }.bind(this));
      }
      else {
        Promise.all([
          tredis.del('musichub.device.' + deviceId + '.current'),
          tredis.del('musichub.device.' + deviceId + '.duration'),
          tredis.del('musichub.device.' + deviceId + '.playing'),
          tredis.del('musichub.device.' + deviceId + '.paused'),
          tredis.del('musichub.device.' + deviceId + '.fromtime'),
          tredis.del('musichub.device.' + deviceId + '.position'),
          tredis.del('musichub.device.' + deviceId + '.queue')
        ]).then(function() {
        	this.emit('Next', deviceId, null);
          resolve(null);
        }.bind(this));
      }
    }.bind(this));
  }.bind(this));
}

Playback.prototype.queue = function(deviceId, trackIds) {
	return new Promise(function(resolve, reject) {
		Promise.all([
		  tredis.del('musichub.device.' + deviceId + '.queue'),
		  tredis.del('musichub.device.' + deviceId + '.queue.device')
		]).then(function() {
		  Promise.each(trackIds, function(trackId) {
		    return this.addTrackToQueue(deviceId, trackId);
		  }.bind(this)).then(function() {
	      tredis.get('musichub.device.' + deviceId + '.playing').then(function(playing) {
	        if (Boolean(parseInt(playing))) {
	          this.requestNext(deviceId);
	          resolve();
	        }
	        else {
	          tredis.set('musichub.device.' + deviceId + '.playing', 0).then(function() {
	            this.next(deviceId);
	            resolve();
	          }.bind(this));
	        }
	      }.bind(this));
		  }.bind(this));
		}.bind(this));
	}.bind(this));
}

module.exports = Playback;<commit_msg>Redis playback clustering<commit_after>var util = require('util');
var events = require('events');
var config = require('config');
var redis = require('redis-scanstreams')(require('redis')).createClient(6379, config.get('redis.host'));
var redisChan = require('redis').createClient(6379, config.get('redis.host'));
var tredis = require('then-redis').createClient(config.get('redis'));
var Promise = require('bluebird');
var models = require('./models');

function Playback() {
	this.dispatch();
	redisChan.subscribe('musicpicker.playback');
}

util.inherits(Playback, events.EventEmitter);

Playback.prototype.dispatch = function() {
	redisChan.on('message', function(channel, message) {
		if (channel !== 'musicpicker.playback') {
			return;
		}
		var message = JSON.parse(message);
		this.emit(message.Event, message.Device, message.Options);
	}.bind(this));
};

Playback.prototype.publish = function(eventName, deviceId, opts) {
	redis.publish('musicpicker.playback', JSON.stringify({
		Device: deviceId,
		Event: eventName,
		Options: opts
	}));
};

Playback.prototype.state = function (deviceId) {
  return new Promise(function(resolve, reject) {
    Promise.props({
      Connected: tredis.exists('musichub.device.' + deviceId + '.connection'),
      Current: tredis.get('musichub.device.' + deviceId + '.current'),
      Duration: tredis.get('musichub.device.' + deviceId + '.duration'),
      Playing: tredis.get('musichub.device.' + deviceId + '.playing'),
      Paused: tredis.get('musichub.device.' + deviceId + '.paused'),
      FromTime: tredis.get('musichub.device.' + deviceId + '.fromtime'),
      Position: tredis.get('musichub.device.' + deviceId + '.position'),
      Queue: tredis.lrange('musichub.device.' + deviceId + '.queue', 0, -1)
    }).then(function(result) {
      resolve({
        Device: deviceId,
        Connected: Boolean(parseInt(result.Connected)),
        Current: parseInt(result.Current),
        Duration: parseInt(result.Duration),
        Playing: Boolean(parseInt(result.Playing)),
        Paused: Boolean(parseInt(result.Paused)),
        FromTime: parseInt(result.FromTime),
        Position: parseInt(result.Position),
        Queue: result.Queue.map(function(item) {
          return parseInt(item);
        })
      });
    });
  });
}

Playback.prototype.updateState = function (deviceId) {
  return new Promise(function(resolve, reject) {
    Promise.props({
      current: tredis.lpop('musichub.device.' + deviceId + '.queue'),
      currentDeviceTrack: tredis.lpop('musichub.device.' + deviceId + '.queue.device'),
    }).then(function(props) {
      tredis.set('musichub.device.' + deviceId + '.current', props.current).then(function() {
        new models.DeviceTrack({
          DeviceId: deviceId,
          TrackId: props.current
        }).fetch().then(function(dt) {
          tredis.set('musichub.device.' + deviceId + '.duration', dt.get('TrackDuration')).then(function() {
            resolve(props.currentDeviceTrack);
          })
        });
      });
    })
  });
}

Playback.prototype.updatePosition = function (deviceId) {
  return new Promise(function(resolve, reject) {
    tredis.get('musichub.device.' + deviceId + '.position').then(function(currentPosition) {
      tredis.get('musichub.device.' + deviceId + '.fromtime').then(function(fromTime) {
        var newPosition = parseInt(currentPosition) + (Date.now() - parseInt(fromTime));
        tredis.set('musichub.device.' + deviceId + '.position', newPosition).then(function() {
          resolve();
        });
      });
    });
  });
}

Playback.prototype.addTrackToQueue = function (deviceId, trackId) {
  return new Promise(function(resolve, reject) {
    new models.DeviceTrack({
      DeviceId: deviceId,
      TrackId: trackId
    }).fetch().then(function(dt) {
      var deviceTrackId = dt.get('DeviceTrackId');
      Promise.all([
        tredis.rpush('musichub.device.' + deviceId + '.queue', trackId),
        tredis.rpush('musichub.device.' + deviceId + '.queue.device', deviceTrackId)
      ]).then(function() {
        resolve();
      })
    });
  });
}

Playback.prototype.requestNext = function (deviceId) {
  return new Promise(function(resolve, reject) {
    tredis.set('musichub.device.' + deviceId + '.playing', 0).then(function() {
    	this.publish('RequestNext', deviceId);
    	resolve();
    }.bind(this));
  }.bind(this));
}

Playback.prototype.play = function (deviceId) {
  return new Promise(function(resolve, reject) {
    Promise.all([
      tredis.set('musichub.device.' + deviceId + '.playing', 1),
      tredis.set('musichub.device.' + deviceId + '.paused', 0),
      tredis.set('musichub.device.' + deviceId + '.fromtime', Date.now())
    ]).then(function() {
    	this.publish('Play', deviceId);
    	resolve();
    }.bind(this));
  }.bind(this));
}

Playback.prototype.pause = function(deviceId) {
	return new Promise(function(resolve, reject) {
		Promise.all([
	    tredis.set('musichub.device.' + deviceId + '.paused', 1),
	    this.updatePosition(deviceId)
	  ]).then(function() {
	  	this.publish('Pause', deviceId);
	  	resolve();
	  }.bind(this));
	}.bind(this));
}

Playback.prototype.next = function (deviceId) {
  return new Promise(function(resolve, reject) {
    tredis.llen('musichub.device.' + deviceId + '.queue').then(function(queueLen) {
      if (parseInt(queueLen) > 0) {
        Promise.all([
          tredis.set('musichub.device.' + deviceId + '.playing', 1),
          tredis.set('musichub.device.' + deviceId + '.paused', 0)
        ]).then(function() {
          this.updateState(deviceId).then(function(currentDeviceTrack) {
            tredis.set('musichub.device.' + deviceId + '.fromtime', Date.now()).then(function() {
              tredis.set('musichub.device.' + deviceId + '.position', 0).then(function() {
              	this.publish('Next', deviceId, currentDeviceTrack);
                resolve(currentDeviceTrack);
              }.bind(this));
            }.bind(this));
          }.bind(this));
        }.bind(this));
      }
      else {
        Promise.all([
          tredis.del('musichub.device.' + deviceId + '.current'),
          tredis.del('musichub.device.' + deviceId + '.duration'),
          tredis.del('musichub.device.' + deviceId + '.playing'),
          tredis.del('musichub.device.' + deviceId + '.paused'),
          tredis.del('musichub.device.' + deviceId + '.fromtime'),
          tredis.del('musichub.device.' + deviceId + '.position'),
          tredis.del('musichub.device.' + deviceId + '.queue')
        ]).then(function() {
        	this.publish('Next', deviceId, null);
          resolve(null);
        }.bind(this));
      }
    }.bind(this));
  }.bind(this));
}

Playback.prototype.queue = function(deviceId, trackIds) {
	return new Promise(function(resolve, reject) {
		Promise.all([
		  tredis.del('musichub.device.' + deviceId + '.queue'),
		  tredis.del('musichub.device.' + deviceId + '.queue.device')
		]).then(function() {
		  Promise.each(trackIds, function(trackId) {
		    return this.addTrackToQueue(deviceId, trackId);
		  }.bind(this)).then(function() {
	      tredis.get('musichub.device.' + deviceId + '.playing').then(function(playing) {
	        if (Boolean(parseInt(playing))) {
	          this.requestNext(deviceId);
	          resolve();
	        }
	        else {
	          tredis.set('musichub.device.' + deviceId + '.playing', 0).then(function() {
	            this.next(deviceId);
	            resolve();
	          }.bind(this));
	        }
	      }.bind(this));
		  }.bind(this));
		}.bind(this));
	}.bind(this));
}

module.exports = Playback;<|endoftext|>