<commit_before>export Icon from './components/base/icons/Icon';

export Notification from './components/containers/notification/Notification';
export LightNotification from './components/containers/notification/LightNotification';
export MessageNotification from './components/containers/notification/MessageNotification';
export {
  InlineMessage,
  Message
} from './components/containers/notification/Message';
export Drawer from './components/containers/drawer/Drawer';
export Fieldset from './components/containers/fieldset/Fieldset';
export Tooltip from './components/containers/tooltip/Tooltip';
export Popover from './components/containers/popover/Popover';
export Modal from './components/containers/modal/Modal';
export ModalButtonContainer from './components/containers/modal/ModalButtonContainer';
export SlidingPane from './components/containers/sliding-pane/SlidingPane';
export Menu from './components/containers/menu/Menu';
export TabPanel from './components/containers/tabPanels/TabPanel';
export StripedContainer from './components/containers/striped-container/StripedContainer';
export Heading from './components/containers/heading/Heading';
export Table from './components/containers/table/Table';
export ResponsiveTable from './components/containers/table/ResponsiveTable';

export Control from './components/controls/Control';
export AdditionalHelp from './components/controls/AdditionalHelp';
export Label from './components/controls/label/Label';
export Textbox from './components/controls/textbox/Textbox';
export Textarea from './components/controls/textbox/Textarea';
export MaskedTextbox from './components/controls/textbox/MaskedTextbox';
export Button from './components/controls/buttons/Button';
export ActionButton from './components/controls/buttons/ActionButton';
export ToggleButton from './components/controls/buttons/ToggleButton';
export DropdownButton from './components/controls/buttons/DropdownButton';
export OutlineButton from './components/controls/buttons/OutlineButton';
export LinkButton from './components/controls/buttons/LinkButton';
export PopoverLink from './components/controls/buttons/PopoverLink';
export AnswerButton from './components/controls/answer-group/AnswerButton';
export AnswerGroup from './components/controls/answer-group/AnswerGroup';
export RadioGroup from './components/controls/radio-buttons/RadioGroup';
export RadioButton from './components/controls/radio-buttons/RadioButton';
export Dropdown from './components/controls/dropdown/Dropdown';
export Checkbox from './components/controls/checkbox/Checkbox';

export SideNav from './components/navigation/sidenav/SideNav';
export HorizontalNav from './components/navigation/horizontalnav/HorizontalNav';

export DatePicker from './components/patterns/datepicker/DatePicker';
export DateInput from './components/patterns/dateInput/DateInput';
export Incrementer from './components/patterns/incrementer/Incrementer';
export screenReaderOnly from './components/patterns/screenReaderOnly/screenReaderOnly';
export CallToAction from './components/patterns/callToAction/CallToAction';
export LightCallToAction from './components/patterns/callToAction/LightCallToAction';
export Action from './components/patterns/callToAction/Action';
export ProgressTracker from './components/patterns/progress-tracker/ProgressTracker';
export StarRating from './components/patterns/starRating/StarRating';

export Spinner from './components/base/spinner/Spinner';

export Fade from './components/util/Fade';
export generateAlphaName from './components/util/generateAlphaName';
export { useWindowWidth } from './components/util/useWindowWidth';
export withWindowSize from './components/util/withWindowSize';
export StyleReset from './components/util/StyleReset';
export { useTheme } from './components/util/useTheme';
export { isValidEmail } from './components/util/isValidEmail';
<commit_msg>Update: exports Star Rating Explaination component<commit_after>export Icon from './components/base/icons/Icon';

export Notification from './components/containers/notification/Notification';
export LightNotification from './components/containers/notification/LightNotification';
export MessageNotification from './components/containers/notification/MessageNotification';
export {
  InlineMessage,
  Message
} from './components/containers/notification/Message';
export Drawer from './components/containers/drawer/Drawer';
export Fieldset from './components/containers/fieldset/Fieldset';
export Tooltip from './components/containers/tooltip/Tooltip';
export Popover from './components/containers/popover/Popover';
export Modal from './components/containers/modal/Modal';
export ModalButtonContainer from './components/containers/modal/ModalButtonContainer';
export SlidingPane from './components/containers/sliding-pane/SlidingPane';
export Menu from './components/containers/menu/Menu';
export TabPanel from './components/containers/tabPanels/TabPanel';
export StripedContainer from './components/containers/striped-container/StripedContainer';
export Heading from './components/containers/heading/Heading';
export Table from './components/containers/table/Table';
export ResponsiveTable from './components/containers/table/ResponsiveTable';

export Control from './components/controls/Control';
export AdditionalHelp from './components/controls/AdditionalHelp';
export Label from './components/controls/label/Label';
export Textbox from './components/controls/textbox/Textbox';
export Textarea from './components/controls/textbox/Textarea';
export MaskedTextbox from './components/controls/textbox/MaskedTextbox';
export Button from './components/controls/buttons/Button';
export ActionButton from './components/controls/buttons/ActionButton';
export ToggleButton from './components/controls/buttons/ToggleButton';
export DropdownButton from './components/controls/buttons/DropdownButton';
export OutlineButton from './components/controls/buttons/OutlineButton';
export LinkButton from './components/controls/buttons/LinkButton';
export PopoverLink from './components/controls/buttons/PopoverLink';
export AnswerButton from './components/controls/answer-group/AnswerButton';
export AnswerGroup from './components/controls/answer-group/AnswerGroup';
export RadioGroup from './components/controls/radio-buttons/RadioGroup';
export RadioButton from './components/controls/radio-buttons/RadioButton';
export Dropdown from './components/controls/dropdown/Dropdown';
export Checkbox from './components/controls/checkbox/Checkbox';

export SideNav from './components/navigation/sidenav/SideNav';
export HorizontalNav from './components/navigation/horizontalnav/HorizontalNav';

export DatePicker from './components/patterns/datepicker/DatePicker';
export DateInput from './components/patterns/dateInput/DateInput';
export Incrementer from './components/patterns/incrementer/Incrementer';
export screenReaderOnly from './components/patterns/screenReaderOnly/screenReaderOnly';
export CallToAction from './components/patterns/callToAction/CallToAction';
export LightCallToAction from './components/patterns/callToAction/LightCallToAction';
export Action from './components/patterns/callToAction/Action';
export ProgressTracker from './components/patterns/progress-tracker/ProgressTracker';
export StarRating from './components/patterns/starRating/StarRating';
export StarRatingExplanation from './components/patterns/starRating/StarRatingExplanation';

export Spinner from './components/base/spinner/Spinner';

export Fade from './components/util/Fade';
export generateAlphaName from './components/util/generateAlphaName';
export { useWindowWidth } from './components/util/useWindowWidth';
export withWindowSize from './components/util/withWindowSize';
export StyleReset from './components/util/StyleReset';
export { useTheme } from './components/util/useTheme';
export { isValidEmail } from './components/util/isValidEmail';
<|endoftext|>


<commit_before>export class GuidedTourServiceClient {

  /**
   *
   * @param message
   * @param duration    in ms
   * @param position
   *      'center'
   *      horizontal placement: 'left' 'right' 'horizontal-align-CSS_SELECTOR'
   *      vertical placement: 'top' 'bottom' 'vertical-align-CSS_SELECTOR'
   *      can combined one vertical with one horizontal, *-align-* being at the end like 'left-vertical-align-#menu'
   * @param size        small medium big
   * @returns {Promise}
   */
  static alert(message, duration, position, size) {
    return new Promise(resolve => {
      $("#guided-tour-overlapp").addClass("grayed");
      $("#message").addClass("visible");
      $("#guide-progress-bar").addClass("visible");

      let height, width;
      if (size == "big") {
        width = 600;
        height = 400;
      } else if (size == "medium") {
        width = 300;
        height = 200;
      } else if (size == "small") {
        width = 150;
        height = 100;
      }

      let windowWidth = window.innerWidth;
      let windowHeight = window.innerHeight;

      let top, left;
      if (position == "center") {
        top = windowHeight / 2 - height / 2;
        left = windowWidth / 2 - width / 2

      } else if (position.indexOf("top") != -1) {
        top = windowHeight * 0.05;
        left = windowWidth - (windowHeight - top) * windowWidth / windowHeight;
      } else if (position.indexOf("bottom") != -1) {
        top = windowHeight * 0.95 - height;
        left = (windowHeight - top - height) * windowWidth / windowHeight
      }
      if (position.indexOf("right") != -1) {
        left = windowWidth - left - width;
      }
      $("#message").width(width);
      $("#message").height(height);

      $("#message-content").html(message);
      $("#message").css('left', left);
      $("#message").css('top', top);

      var i = 100;
      var counterBack = setInterval(function () {
        i--;
        if (i > 0) {
          $('#guide-progress-bar').css('width', i + '%');
        } else {
          clearInterval(counterBack);
          $("#message").removeClass("visible");
          $("#guide-progress-bar").removeClass("visible");
          $("#guided-tour-overlapp").removeClass("grayed");
          resolve()
        }
      }, duration / 100);

    })
  }

  /**
   * @summary only support one selection so far (but will work on multiple select)
   * @param labelToClickOn    the exact label of the customSelect
   * @param optionToSelect    the exact label of the option to select
   */
  static selectOption(labelToClickOn, optionToSelect, delay) {
    return new Promise(resolve => {
      let target = `[for='${labelToClickOn}']`;
      GuidedTourServiceClient.scrollIfTargetOutOfWindow(target)
        .then(() => GuidedTourServiceClient.clickOn(target, delay))
        .then(() => GuidedTourServiceClient.waitFor(`.popover :contains(${optionToSelect})`))
        .then(() => GuidedTourServiceClient.scrollIfTargetOutOfWindow(GuidedTourServiceClient.getJqueryObject(".popover")))
        .then(() => GuidedTourServiceClient.sleep(delay))
        .then(() => {
          return new Promise(resolve => {
            let allOptionsThatMatches = $(`.popover-content .custom-select-options .list-group .list-group-item:contains(${optionToSelect}) input`).toArray();
            GuidedTourServiceClient._selectionOption(allOptionsThatMatches, resolve, delay);
          })
        })
        .then(() => resolve())
    })
  }

  static _selectionOption(allOptionsThatMatches, resolve, delay) {
    GuidedTourServiceClient.clickOn(allOptionsThatMatches[0], delay).then(() => {
      allOptionsThatMatches.pop(0);
      if (allOptionsThatMatches.length == 0)
        resolve()
      else
        GuidedTourServiceClient._selectionOption(allOptionsThatMatches, resolve, delay)
    });
  }

  /**
   *
   * @param datetimepicker  CSS selector of jQuery object
   * @param day             string, MM/DD/YYYY
   * @param hours           string, format HH
   * @returns {Promise}
   */
  static selectDate(datetimepickerSelector, date, month, hours, delay) {
    return new Promise(resolve => {
      let datetimepicker = GuidedTourServiceClient.getJqueryObject(datetimepickerSelector);
      GuidedTourServiceClient.clickOn(datetimepicker, delay)
        .then(() => GuidedTourServiceClient.sleep(100))
        .then(() => {
          return new Promise(resolve => {
            let datetimepickerData = $(datetimepicker).data("DateTimePicker");
            // if (datetimepicker)
            datetimepickerData.show();
            resolve()
          })
        })
        .then(() => GuidedTourServiceClient.waitFor(".bootstrap-datetimepicker-widget"))
        .then(() => GuidedTourServiceClient.sleep(delay * 2))
        //if month and date
        .then(() => {
          return new Promise(resolve => {
            if (date && month) {
              GuidedTourServiceClient.clickOn(`.datepicker .datepicker-days .picker-switch`, delay)
                .then(() => GuidedTourServiceClient.sleep(delay * 2))
                .then(() => GuidedTourServiceClient.clickOn(`.datepicker .datepicker-months [data-action=selectMonth]:contains(${month})`, delay))
                .then(() => GuidedTourServiceClient.sleep(delay))
                .then(() => GuidedTourServiceClient.clickOn(`.datepicker .datepicker-days [data-day='${date}']`, delay))
                .then(() => GuidedTourServiceClient.sleep(delay))
                .then(() => resolve())
            } else {
              resolve()
            }
          });
        })
        //if hours
        .then(() => {
          return new Promise(resolve => {
            if (hours)
              GuidedTourServiceClient.clickOn(`.timepicker .timepicker-hour[data-action=showHours]`, delay)
                .then(() => GuidedTourServiceClient.waitFor(`.timepicker .timepicker-hours [data-action="selectHour"]:contains(${hours})`))
                .then(() => GuidedTourServiceClient.sleep(delay))
                .then(() => GuidedTourServiceClient.clickOn(`.timepicker .timepicker-hours [data-action="selectHour"]:contains(${hours})`, delay))
                .then(() => GuidedTourServiceClient.sleep(delay))
                .then(() => resolve());
            else
              resolve()
          })
        })
        .then(() => {
          $(datetimepicker).data("DateTimePicker").hide();
          resolve()
        });
    })
  }

  /**
   * @summary look into the whole dom for a content that match given param
   * @param content
   * @returns {*}
   */
  static findComponentByContent(content) {
    let component = $(`:contains(${content})`);
    return component[component.length - 1];
  }

  static _typeText(text, target, resolve, delayBetweenChar) {
    var $target = $(target);
    $target.addClass("filling-input");
    $target.val($target.val() + text.charAt(0));
    text = text.substr(1);
    if (text.length != 0)
      GuidedTourServiceClient.sleep(delayBetweenChar)
        .then(() => GuidedTourServiceClient._typeText(text, target, resolve, delayBetweenChar));
    else {
      $target.trigger('change');
      $target.removeClass("filling-input");
      resolve()
    }
  }

  static typeText(text, target, delayBetweenChar) {
    return new Promise((resolve) => {
      var $target = $(target);
      $target.val("");
      GuidedTourServiceClient.scrollIfTargetOutOfWindow(target)
        .then(() => GuidedTourServiceClient._typeText(text, target, resolve, delayBetweenChar))
    });
  }

  static typeEquipment(qt, label, delay) {
    return new Promise(resolve => {
      let $label = $(`.equipment-wrapper :contains(${label})`);
      let parent = $($label[$label.length - 1]).parent();
      GuidedTourServiceClient.typeText(qt.toString(), $(parent.children()[0]).find("input")[0], delay)
        .then(() => resolve())
    })
  }

  static _waitFor(query, resolve) {
    console.debug("waiting for", query);
    if (query instanceof jQuery) {
      if ($(query.selector).length != 0) {
        resolve()
      } else {
        GuidedTourServiceClient.sleep(100).then(() => {
          GuidedTourServiceClient._waitFor(query.selector, resolve)
        })
      }
    } else if ($(`body:contains(${query})`).length != 0) {
      resolve()
    } else if ($(`${query}`).length != 0) {
      resolve();
    } else {
      GuidedTourServiceClient.sleep(100).then(() => {
        GuidedTourServiceClient._waitFor(query, resolve)
      })
    }
  }

  static isPresent(query) {
    if ($(`body:contains(${query})`).length != 0) {
      return true;
    } else if ($(`${query}`).length != 0) {
      return true;
    } else {
      return false
    }
  }

  static waitFor(query) {
    return new Promise(resolve => {
      GuidedTourServiceClient._waitFor(query, resolve)
    })
  }

  /**
   * @param target  jQuery object
   * @returns {Promise}
   */
  static scrollIfTargetOutOfWindow($target) {
    return new Promise(resolve => {
      $target = GuidedTourServiceClient.getJqueryObject($target);
      let windowHeight = window.innerHeight;
      let targetYPosition = $target[0].getBoundingClientRect().y;
      let targetOffset = $target.offset().top;
      let targetHeight = $target[0].getBoundingClientRect().height;

      let scrollToY;
      if (targetYPosition < 60) {
        scrollToY = targetOffset - (50 + 20); //50px is the top nav bar height, 10px is a bit of margin

      } else if (targetYPosition > windowHeight - targetHeight) {
        scrollToY = targetOffset - windowHeight / 2 + targetHeight / 2
      }
      if (scrollToY) {
        window.scrollTo(0, scrollToY);
        GuidedTourServiceClient.sleep(200).then(() => {
          resolve()
        });
      } else
        resolve()

    })
  }

  /**
   * @param cssSelector can be string CSS selector, a jQuery object, an HTML object
   * @returns {jQuery|HTMLElement}
   */
  static getJqueryObject(cssSelector) {
    return (typeof cssSelector == "string") ? $(`${cssSelector}`) : (typeof cssSelector == 'jQuery') ? cssSelector : $(cssSelector);
  }

  /**
   * @param cssSelector either a CSS selector or a jQuery object
   */
  static clickOn(cssSelector, delay) {
    return new Promise(resolveClickOn => {
      let component = GuidedTourServiceClient.getJqueryObject(cssSelector);
      GuidedTourServiceClient.waitFor(component).then(resolveWaiting => {
        let componentPosition = component[0].getBoundingClientRect() //component.offset(); //absolute top left position
        let componentHeight = component.height();
        let componentWidth = component.width();
        let clickHighlight = $("#click-highlight");
        let top = componentPosition.y + componentHeight / 2 - 75 / 2; //the circle fit in a 75px square
        let left = componentPosition.x + componentWidth / 2 - 75 / 2;
        clickHighlight.css("top", top);
        clickHighlight.css("left", left);

        $("#guided-tour-overlapp").addClass("grayed");
        clickHighlight.addClass("visible");

        GuidedTourServiceClient.scrollIfTargetOutOfWindow(cssSelector)
          .then(() => GuidedTourServiceClient.sleep(delay))
          .then(() => {
            $(cssSelector).click();
            $("#guided-tour-overlapp").removeClass("grayed");
            clickHighlight.removeClass("visible");
            resolveClickOn()
          });

        //we don't really care about finishing waitingFor promise as the clickOn one is the one we listen to
      })
    })

  }

  static openMenu(toClose = false) {
    return new Promise(resolve => {
      let isOpen = $(".top-bar button.sidebar-toggle .sidebar-arrow").hasClass("mdi-arrow-left");
      if (!toClose && !isOpen || toClose && isOpen)
        GuidedTourServiceClient.clickOn(".top-bar button.sidebar-toggle .sidebar-arrow").then(resolve)
      else
        resolve()
    })
  }

  static logout(speed) {
    return new Promise(resolve => {
      GuidedTourServiceClient.openMenu()
        .then(() => GuidedTourServiceClient.sleep(200 * speed))
        .then(() => GuidedTourServiceClient.clickOn("[href='#settings-dropdown']", speed * 300))
        .then(() => GuidedTourServiceClient.sleep(200 * speed))
        .then(() => GuidedTourServiceClient.clickOn("[href='/logout']", speed * 300))
        .then(() => GuidedTourServiceClient.waitFor(".at-pwd-form"))
        .then(() => resolve())
    })
  }

  static instantLogout() {
    return new Promise(resolve => {
      if (GuidedTourServiceClient.isPresent(".at-pwd-form")) {
        console.debug("instantLogout: already logged out");
        resolve();
        return
      }
      Meteor.logout();
      Router.go('home');
      console.debug("instantLogout: logging out and waiting for");
      GuidedTourServiceClient.waitFor(".at-pwd-form")
        .then(() => resolve())
    })
  }

  static login(speed, user) {
    return new Promise(resolve => {
      GuidedTourServiceClient.waitFor(".at-pwd-form")
        .then(() => GuidedTourServiceClient.typeText(user.email, "#at-field-email", speed * 50))
        .then(() => GuidedTourServiceClient.typeText(user.pwd, "#at-field-password", speed * 50))
        .then(() => GuidedTourServiceClient.clickOn("#sidebar #at-pwd-form .submit", speed * 200))
        .then(() => GuidedTourServiceClient.waitFor("Home"))
        .then(() => resolve())
    })
  }

  static closeMenu() {
    return GuidedTourServiceClient.openMenu(true)
  }

  static sleep(time) {
    return new Promise((resolve) => setTimeout(resolve, time));
  }

}<commit_msg>feat (guided tour) alert got new positions !<commit_after>export class GuidedTourServiceClient {

  /**
   *
   * @param message
   * @param duration    in ms
   * @param position
   *      'center'
   *      horizontal placement: 'left' 'right' 'vertical-align-CSS_SELECTOR'
   *      vertical placement: 'top' 'bottom' 'horizontal-align-CSS_SELECTOR'
   *      can combined one vertical with one horizontal, *-align-* being at the end like 'left-vertical-align-#menu'
   * @param size        small medium big
   * @returns {Promise}
   */
  static alert(message, duration, position, size) {
    $("#guided-tour-overlapp").addClass("visible");
    return new Promise(resolve => {
      $("#guided-tour-overlapp").addClass("grayed");
      $("#message").addClass("visible");
      $("#guide-progress-bar").addClass("visible");

      let height, width;
      if (size == "big") {
        width = 600;
        height = 400;
      } else if (size == "medium") {
        width = 300;
        height = 200;
      } else if (size == "small") {
        width = 150;
        height = 100;
      }

      let windowWidth = window.innerWidth;
      let windowHeight = window.innerHeight;

      let top, left;
      if (position == "center") {
        top = windowHeight / 2 - height / 2;
        left = windowWidth / 2 - width / 2
      }

      if (position.indexOf("top") != -1) {
        top = windowHeight * 0.05;
        left = windowWidth - (windowHeight - top) * windowWidth / windowHeight;
      } else if (position.indexOf("bottom") != -1) {
        top = windowHeight * 0.95 - height;
        left = (windowHeight - top - height) * windowWidth / windowHeight
      }

      if(position.indexOf("horizontal-align-") != -1){
        let cssSelector = position.substring(position.indexOf("horizontal-align-") + "horizontal-align-".length, position.length);
        top = $(cssSelector)[0].getBoundingClientRect().y - height / 2;
        left = windowWidth * 0.05
      }

      if(position.indexOf("vertical-align-") != -1){
        let cssSelector = position.substring(position.indexOf("vertical-align-") + "vertical-align-".length, position.length);
        left = $(cssSelector)[0].getBoundingClientRect().x - width / 2 + $(cssSelector)[0].getBoundingClientRect().width / 2;
      }

      if (position.indexOf("right") != -1) {
        left = windowWidth - left - width;
      } else if (position.indexOf("left") != -1) {
        //nothing to do there
      }

      console.log("top",top);

      $("#message").width(width);
      $("#message").height(height);

      $("#message-content").html(message);
      $("#message").css('left', left);
      $("#message").css('top', top);

      var i = 100;
      var counterBack = setInterval(function () {
        i--;
        if (i > 0) {
          $('#guide-progress-bar').css('width', i + '%');
        } else {
          clearInterval(counterBack);
          $("#message").removeClass("visible");
          $("#guide-progress-bar").removeClass("visible");
          $("#guided-tour-overlapp").removeClass("grayed");
          resolve()
        }
      }, duration / 100);

    })
  }

  /**
   * @summary only support one selection so far (but will work on multiple select)
   * @param labelToClickOn    the exact label of the customSelect
   * @param optionToSelect    the exact label of the option to select
   */
  static selectOption(labelToClickOn, optionToSelect, delay) {
    return new Promise(resolve => {
      let target = `[for='${labelToClickOn}']`;
      GuidedTourServiceClient.scrollIfTargetOutOfWindow(target)
        .then(() => GuidedTourServiceClient.clickOn(target, delay))
        .then(() => GuidedTourServiceClient.waitFor(`.popover :contains(${optionToSelect})`))
        .then(() => GuidedTourServiceClient.scrollIfTargetOutOfWindow(GuidedTourServiceClient.getJqueryObject(".popover")))
        .then(() => GuidedTourServiceClient.sleep(delay))
        .then(() => {
          return new Promise(resolve => {
            let allOptionsThatMatches = $(`.popover-content .custom-select-options .list-group .list-group-item:contains(${optionToSelect}) input`).toArray();
            GuidedTourServiceClient._selectionOption(allOptionsThatMatches, resolve, delay);
          })
        })
        .then(() => resolve())
    })
  }

  static _selectionOption(allOptionsThatMatches, resolve, delay) {
    GuidedTourServiceClient.clickOn(allOptionsThatMatches[0], delay).then(() => {
      allOptionsThatMatches.pop(0);
      if (allOptionsThatMatches.length == 0)
        resolve()
      else
        GuidedTourServiceClient._selectionOption(allOptionsThatMatches, resolve, delay)
    });
  }

  /**
   *
   * @param datetimepicker  CSS selector of jQuery object
   * @param day             string, MM/DD/YYYY
   * @param hours           string, format HH
   * @returns {Promise}
   */
  static selectDate(datetimepickerSelector, date, month, hours, delay) {
    return new Promise(resolve => {
      let datetimepicker = GuidedTourServiceClient.getJqueryObject(datetimepickerSelector);
      GuidedTourServiceClient.clickOn(datetimepicker, delay)
        .then(() => GuidedTourServiceClient.sleep(100))
        .then(() => {
          return new Promise(resolve => {
            let datetimepickerData = $(datetimepicker).data("DateTimePicker");
            // if (datetimepicker)
            datetimepickerData.show();
            resolve()
          })
        })
        .then(() => GuidedTourServiceClient.waitFor(".bootstrap-datetimepicker-widget"))
        .then(() => GuidedTourServiceClient.sleep(delay * 2))
        //if month and date
        .then(() => {
          return new Promise(resolve => {
            if (date && month) {
              GuidedTourServiceClient.clickOn(`.datepicker .datepicker-days .picker-switch`, delay)
                .then(() => GuidedTourServiceClient.sleep(delay * 2))
                .then(() => GuidedTourServiceClient.clickOn(`.datepicker .datepicker-months [data-action=selectMonth]:contains(${month})`, delay))
                .then(() => GuidedTourServiceClient.sleep(delay))
                .then(() => GuidedTourServiceClient.clickOn(`.datepicker .datepicker-days [data-day='${date}']`, delay))
                .then(() => GuidedTourServiceClient.sleep(delay))
                .then(() => resolve())
            } else {
              resolve()
            }
          });
        })
        //if hours
        .then(() => {
          return new Promise(resolve => {
            if (hours)
              GuidedTourServiceClient.clickOn(`.timepicker .timepicker-hour[data-action=showHours]`, delay)
                .then(() => GuidedTourServiceClient.waitFor(`.timepicker .timepicker-hours [data-action="selectHour"]:contains(${hours})`))
                .then(() => GuidedTourServiceClient.sleep(delay))
                .then(() => GuidedTourServiceClient.clickOn(`.timepicker .timepicker-hours [data-action="selectHour"]:contains(${hours})`, delay))
                .then(() => GuidedTourServiceClient.sleep(delay))
                .then(() => resolve());
            else
              resolve()
          })
        })
        .then(() => {
          $(datetimepicker).data("DateTimePicker").hide();
          resolve()
        });
    })
  }

  /**
   * @summary look into the whole dom for a content that match given param
   * @param content
   * @returns {*}
   */
  static findComponentByContent(content) {
    let component = $(`:contains(${content})`);
    return component[component.length - 1];
  }

  static _typeText(text, target, resolve, delayBetweenChar) {
    var $target = $(target);
    $target.addClass("filling-input");
    $target.val($target.val() + text.charAt(0));
    text = text.substr(1);
    if (text.length != 0)
      GuidedTourServiceClient.sleep(delayBetweenChar)
        .then(() => GuidedTourServiceClient._typeText(text, target, resolve, delayBetweenChar));
    else {
      $target.trigger('change');
      $target.removeClass("filling-input");
      resolve()
    }
  }

  static typeText(text, target, delayBetweenChar) {
    return new Promise((resolve) => {
      var $target = $(target);
      $target.val("");
      GuidedTourServiceClient.scrollIfTargetOutOfWindow(target)
        .then(() => GuidedTourServiceClient._typeText(text, target, resolve, delayBetweenChar))
    });
  }

  static typeEquipment(qt, label, delay) {
    return new Promise(resolve => {
      let $label = $(`.equipment-wrapper :contains(${label})`);
      let parent = $($label[$label.length - 1]).parent();
      GuidedTourServiceClient.typeText(qt.toString(), $(parent.children()[0]).find("input")[0], delay)
        .then(() => resolve())
    })
  }

  static _waitFor(query, resolve) {
    console.debug("waiting for", query);
    if (query instanceof jQuery) {
      if ($(query.selector).length != 0) {
        resolve()
      } else {
        GuidedTourServiceClient.sleep(100).then(() => {
          GuidedTourServiceClient._waitFor(query.selector, resolve)
        })
      }
    } else if ($(`body:contains(${query})`).length != 0) {
      resolve()
    } else if ($(`${query}`).length != 0) {
      resolve();
    } else {
      GuidedTourServiceClient.sleep(100).then(() => {
        GuidedTourServiceClient._waitFor(query, resolve)
      })
    }
  }

  static isPresent(query) {
    if ($(`body:contains(${query})`).length != 0) {
      return true;
    } else if ($(`${query}`).length != 0) {
      return true;
    } else {
      return false
    }
  }

  static waitFor(query) {
    return new Promise(resolve => {
      GuidedTourServiceClient._waitFor(query, resolve)
    })
  }

  /**
   * @param target  jQuery object
   * @returns {Promise}
   */
  static scrollIfTargetOutOfWindow($target) {
    return new Promise(resolve => {
      $target = GuidedTourServiceClient.getJqueryObject($target);
      let windowHeight = window.innerHeight;
      let targetYPosition = $target[0].getBoundingClientRect().y;
      let targetOffset = $target.offset().top;
      let targetHeight = $target[0].getBoundingClientRect().height;

      let scrollToY;
      if (targetYPosition < 60) {
        scrollToY = targetOffset - (50 + 20); //50px is the top nav bar height, 10px is a bit of margin

      } else if (targetYPosition > windowHeight - targetHeight) {
        scrollToY = targetOffset - windowHeight / 2 + targetHeight / 2
      }
      if (scrollToY) {
        window.scrollTo(0, scrollToY);
        GuidedTourServiceClient.sleep(200).then(() => {
          resolve()
        });
      } else
        resolve()

    })
  }

  /**
   * @param cssSelector can be string CSS selector, a jQuery object, an HTML object
   * @returns {jQuery|HTMLElement}
   */
  static getJqueryObject(cssSelector) {
    return (typeof cssSelector == "string") ? $(`${cssSelector}`) : (typeof cssSelector == 'jQuery') ? cssSelector : $(cssSelector);
  }

  /**
   * @param cssSelector either a CSS selector or a jQuery object
   */
  static clickOn(cssSelector, delay) {
    return new Promise(resolveClickOn => {
      let component = GuidedTourServiceClient.getJqueryObject(cssSelector);
      GuidedTourServiceClient.waitFor(component).then(resolveWaiting => {
        let componentPosition = component[0].getBoundingClientRect() //component.offset(); //absolute top left position
        let componentHeight = component.height();
        let componentWidth = component.width();
        let clickHighlight = $("#click-highlight");
        let top = componentPosition.y + componentHeight / 2 - 75 / 2; //the circle fit in a 75px square
        let left = componentPosition.x + componentWidth / 2 - 75 / 2;
        clickHighlight.css("top", top);
        clickHighlight.css("left", left);

        $("#guided-tour-overlapp").addClass("grayed");
        clickHighlight.addClass("visible");

        GuidedTourServiceClient.scrollIfTargetOutOfWindow(cssSelector)
          .then(() => GuidedTourServiceClient.sleep(delay))
          .then(() => {
            $(cssSelector).click();
            $("#guided-tour-overlapp").removeClass("grayed");
            clickHighlight.removeClass("visible");
            resolveClickOn()
          });

        //we don't really care about finishing waitingFor promise as the clickOn one is the one we listen to
      })
    })

  }

  static openMenu(toClose = false) {
    return new Promise(resolve => {
      let isOpen = $(".top-bar button.sidebar-toggle .sidebar-arrow").hasClass("mdi-arrow-left");
      if (!toClose && !isOpen || toClose && isOpen)
        GuidedTourServiceClient.clickOn(".top-bar button.sidebar-toggle .sidebar-arrow").then(resolve)
      else
        resolve()
    })
  }

  static logout(speed) {
    return new Promise(resolve => {
      GuidedTourServiceClient.openMenu()
        .then(() => GuidedTourServiceClient.sleep(200 * speed))
        .then(() => GuidedTourServiceClient.clickOn("[href='#settings-dropdown']", speed * 300))
        .then(() => GuidedTourServiceClient.sleep(200 * speed))
        .then(() => GuidedTourServiceClient.clickOn("[href='/logout']", speed * 300))
        .then(() => GuidedTourServiceClient.waitFor(".at-pwd-form"))
        .then(() => resolve())
    })
  }

  static instantLogout() {
    return new Promise(resolve => {
      if (GuidedTourServiceClient.isPresent(".at-pwd-form")) {
        console.debug("instantLogout: already logged out");
        resolve();
        return
      }
      Meteor.logout();
      Router.go('home');
      console.debug("instantLogout: logging out and waiting for");
      GuidedTourServiceClient.waitFor(".at-pwd-form")
        .then(() => resolve())
    })
  }

  static login(speed, user) {
    return new Promise(resolve => {
      GuidedTourServiceClient.waitFor(".at-pwd-form")
        .then(() => GuidedTourServiceClient.typeText(user.email, "#at-field-email", speed * 50))
        .then(() => GuidedTourServiceClient.typeText(user.pwd, "#at-field-password", speed * 50))
        .then(() => GuidedTourServiceClient.clickOn("#sidebar #at-pwd-form .submit", speed * 200))
        .then(() => GuidedTourServiceClient.waitFor("Home"))
        .then(() => resolve())
    })
  }

  static closeMenu() {
    return GuidedTourServiceClient.openMenu(true)
  }

  static sleep(time) {
    return new Promise((resolve) => setTimeout(resolve, time));
  }

}<|endoftext|>


<commit_before>const chalk = require('chalk')
const path = require('path')

const buildFromTemplate = require(path.join(__dirname, 'buildFromTemplate.js'))

module.exports = async function (post) {
  const { comments, subreddit, title } = await post.fetch()
  const participants = comments.map(comment => comment.author.name)

  if (!participants.includes(process.env.REDDIT_USERNAME)) {
    return
  }

  console.log(chalk.red(`${chalk.bold('Found new post:')} ${title} [/r/${subreddit.display_name}]`))

  const comment = buildFromTemplate(post)

  return post.reply(comment)
}
<commit_msg>Fixed participant check<commit_after>const chalk = require('chalk')
const path = require('path')

const buildFromTemplate = require(path.join(__dirname, 'buildFromTemplate.js'))

module.exports = async function (post) {
  const { comments, subreddit, title } = await post.fetch()
  const participants = comments.map(comment => comment.author.name)

  if (participants.includes(process.env.REDDIT_USERNAME)) {
    return
  }

  console.log(chalk.red(`${chalk.bold('Found new post:')} ${title} [/r/${subreddit.display_name}]`))

  const comment = buildFromTemplate(post)

  return post.reply(comment)
}
<|endoftext|>


<commit_before>import React, { useEffect, useRef, useState } from 'react';
import { useContentObj } from 'helpers/hooks';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import {
  changeByUserStatusForThumbs,
  editVideoThumbs,
  likeVideo,
  setDifficulty
} from 'redux/actions/VideoActions';
import Carousel from 'components/Carousel';
import Button from 'components/Button';
import Loading from 'components/Loading';
import VideoPlayer from 'components/VideoPlayer';
import NotFound from 'components/NotFound';
import CheckListGroup from 'components/CheckListGroup';
import PageTab from './PageTab';
import Comments from 'components/Comments';
import Details from './Details';
import NavMenu from './NavMenu';
import ResultModal from './Modals/ResultModal';
import QuestionsBuilder from './QuestionsBuilder';
import ConfirmModal from 'components/Modals/ConfirmModal';
import { fetchedVideoCodeFromURL, stringIsEmpty } from 'helpers/stringHelpers';
import queryString from 'query-string';
import ErrorBoundary from 'components/Wrappers/ErrorBoundary';
import Subjects from 'components/Subjects';
import RewardStatus from 'components/RewardStatus';
import request from 'axios';
import { Color, mobileMaxWidth } from 'constants/css';
import { css } from 'emotion';
import {
  auth,
  fetchPlaylistsContaining,
  handleError,
  loadComments,
  loadSubjects
} from 'helpers/requestHelpers';
import URL from 'constants/URL';

VideoPage.propTypes = {
  authLevel: PropTypes.number,
  canEdit: PropTypes.bool,
  changeByUserStatusForThumbs: PropTypes.func.isRequired,
  dispatch: PropTypes.func.isRequired,
  editVideoThumbs: PropTypes.func.isRequired,
  history: PropTypes.object.isRequired,
  likeVideo: PropTypes.func.isRequired,
  location: PropTypes.object.isRequired,
  match: PropTypes.object.isRequired,
  setDifficulty: PropTypes.func.isRequired,
  userId: PropTypes.number
};

function VideoPage({
  authLevel,
  canEdit,
  changeByUserStatusForThumbs,
  dispatch,
  editVideoThumbs,
  history,
  likeVideo,
  location: { search },
  match: {
    params: { videoId: initialVideoId }
  },
  setDifficulty,
  userId
}) {
  const [changingPage, setChangingPage] = useState(false);
  const [watchTabActive, setWatchTabActive] = useState(true);
  const [currentSlide, setCurrentSlide] = useState(0);
  const [userAnswers, setUserAnswers] = useState([]);
  const [resultModalShown, setResultModalShown] = useState(false);
  const [confirmModalShown, setConfirmModalShown] = useState(false);
  const [onEdit, setOnEdit] = useState(false);
  const [questionsBuilderShown, setQuestionsBuilderShown] = useState(false);
  const [videoId, setVideoId] = useState(initialVideoId);
  const [videoLoading, setVideoLoading] = useState(true);
  const [videoUnavailable, setVideoUnavailable] = useState(false);
  const mounted = useRef(true);
  const CommentInputAreaRef = useRef(null);
  const videoIdRef = useRef(initialVideoId);
  const {
    contentObj,
    contentObj: {
      byUser,
      childComments: comments,
      commentsLoadMoreButton,
      content,
      description,
      difficulty,
      hasHqThumb,
      likes,
      questions,
      stars,
      subjects,
      subjectsLoadMoreButton,
      tags,
      timeStamp,
      title,
      uploader,
      views
    },
    setContentObj,
    onAddTags,
    onAttachStar,
    onDeleteComment,
    onDeleteSubject,
    onEditComment,
    onEditRewardComment,
    onEditSubject,
    onInitContent,
    onLikeComment,
    onLikeContent,
    onLoadSubjectComments,
    onLoadMoreComments,
    onLoadMoreReplies,
    onLoadMoreSubjects,
    onLoadMoreSubjectComments,
    onLoadMoreSubjectReplies,
    onSetDifficulty,
    onSetSubjectDifficulty,
    onUploadComment,
    onUploadReply,
    onUploadSubject
  } = useContentObj({
    type: 'video',
    contentId: Number(videoId)
  });

  useEffect(() => {
    mounted.current = true;
    videoIdRef.current = initialVideoId;
    setChangingPage(true);
    loadVideoPage();
    setVideoId(initialVideoId);
    setChangingPage(false);
    async function loadVideoPage() {
      try {
        const { data } = await request.get(
          `${URL}/video/page?videoId=${videoIdRef.current}`
        );
        const subjectsObj = await loadSubjects({
          type: 'video',
          contentId: videoIdRef.current
        });
        const commentsObj = await loadComments({
          id: videoIdRef.current,
          type: 'video'
        });
        const tags = await fetchPlaylistsContaining({
          videoId: videoIdRef.current
        });
        if (mounted.current) {
          setVideoLoading(false);
          onInitContent({
            content: {
              ...data,
              tags,
              contentId: Number(videoIdRef.current),
              childComments: commentsObj?.comments || [],
              commentsLoadMoreButton: commentsObj?.loadMoreButton || false,
              subjects: subjectsObj?.results || [],
              subjectsLoadMoreButton: subjectsObj?.loadMoreButton || false
            }
          });
        }
      } catch (error) {
        if (error.response) {
          const { data = {} } = error.response;
          if (data.notFound) {
            setVideoUnavailable(true);
          }
        }
        console.error(error.response || error);
      }
    }
    return function cleanUp() {
      mounted.current = false;
    };
  }, [initialVideoId]);

  const { playlist: playlistId } = queryString.parse(search);
  const userIsUploader = uploader?.id === userId;
  const userCanEditThis = canEdit && authLevel >= uploader?.authLevel;
  return (
    <ErrorBoundary
      className={css`
        display: flex;
        justify-content: space-between;
        width: 100%;
        height: 100%;
        margin-top: 1rem;
        @media (max-width: ${mobileMaxWidth}) {
          margin-top: 0;
          flex-direction: column;
        }
      `}
    >

        {(videoLoading || videoUnavailable) && (

            {videoLoading && <Loading text="Loading Video..." />}
            {videoUnavailable && <NotFound text="Video does not exist" />}

        )}
        {!videoLoading && !videoUnavailable && content && (

              <PageTab
                questions={questions}
                watchTabActive={watchTabActive}
                onWatchTabClick={() => setWatchTabActive(true)}
                onQuestionTabClick={() => setWatchTabActive(false)}
              />

                {!questionsBuilderShown && (
                  <VideoPlayer
                    difficulty={difficulty}
                    byUser={!!byUser}
                    key={videoId}
                    hasHqThumb={hasHqThumb}
                    onEdit={onEdit}
                    videoId={videoId}
                    videoCode={content}
                    title={title}
                    uploader={uploader}
                    minimized={!watchTabActive}
                  />
                )}
                {!watchTabActive && questions.length > 0 && (
                  <Carousel
                    allowDrag={false}
                    style={{ marginTop: !!difficulty && '1rem' }}
                    progressBar
                    showQuestionsBuilder={() => setQuestionsBuilderShown(true)}
                    userIsUploader={userIsUploader}
                    userCanEditThis={userCanEditThis}
                    slidesToShow={1}
                    slidesToScroll={1}
                    slideIndex={currentSlide}
                    afterSlide={setCurrentSlide}
                    onFinish={() => setResultModalShown(true)}
                  >
                    {handleRenderSlides()}
                  </Carousel>
                )}
                {!watchTabActive && questions.length === 0 && (

There are no questions yet.

                    {(userIsUploader || userCanEditThis) && (
                      <Button
                        style={{ marginTop: '2rem', fontSize: '2rem' }}
                        success
                        filled
                        onClick={() => setQuestionsBuilderShown(true)}
                      >
                        Add Questions
                      </Button>
                    )}

                )}

               setOnEdit(true)}
                onEditCancel={() => setOnEdit(false)}
                onEditFinish={handleEditVideoPage}
                onDelete={() => setConfirmModalShown(true)}
                setDifficulty={handleSetDifficulty}
                stars={stars}
                tags={tags}
                title={title}
                timeStamp={timeStamp}
                uploader={uploader}
                userId={userId}
                videoId={videoId}
                videoViews={views}
              />
              <RewardStatus
                contentType="video"
                difficulty={byUser ? 5 : 0}
                onCommentEdit={onEditRewardComment}
                style={{
                  fontSize: '1.4rem'
                }}
                stars={stars}
                uploaderName={uploader.username}
              />

            <Subjects
              loadMoreButton={subjectsLoadMoreButton}
              subjects={subjects}
              onLoadMoreSubjects={onLoadMoreSubjects}
              onLoadSubjectComments={onLoadSubjectComments}
              onSubjectEditDone={onEditSubject}
              onSubjectDelete={onDeleteSubject}
              setSubjectDifficulty={onSetSubjectDifficulty}
              uploadSubject={onUploadSubject}
              contentId={videoId}
              type="video"
              rootDifficulty={difficulty}
              commentActions={{
                attachStar: onAttachStar,
                editRewardComment: onEditRewardComment,
                onDelete: onDeleteComment,
                onEditDone: onEditComment,
                onLikeClick: onLikeComment,
                onLoadMoreComments: onLoadMoreSubjectComments,
                onLoadMoreReplies: onLoadMoreSubjectReplies,
                onUploadComment,
                onUploadReply
              }}
            />

                Comment on this video

              <Comments
                autoExpand
                comments={comments}
                inputAreaInnerRef={CommentInputAreaRef}
                inputTypeLabel={'comment'}
                loadMoreButton={commentsLoadMoreButton}
                onAttachStar={onAttachStar}
                onCommentSubmit={onUploadComment}
                onDelete={onDeleteComment}
                onEditDone={onEditComment}
                onLikeClick={onLikeComment}
                onLoadMoreComments={onLoadMoreComments}
                onLoadMoreReplies={onLoadMoreReplies}
                onReplySubmit={onUploadReply}
                onRewardCommentEdit={onEditRewardComment}
                parent={{
                  type: 'video',
                  difficulty,
                  id: Number(videoId),
                  uploader
                }}
                style={{ paddingTop: '1rem' }}
                userId={userId}
              />

            {resultModalShown && (
              <ResultModal
                onHide={() => setResultModalShown(false)}
                numberCorrect={numberCorrect}
                totalQuestions={questions.length}
              />
            )}
            {confirmModalShown && (
              <ConfirmModal
                title="Remove Video"
                onHide={() => setConfirmModalShown(false)}
                onConfirm={handleDeleteVideo}
              />
            )}
            {questionsBuilderShown && (
              <QuestionsBuilder
                questions={questions}
                title={title}
                videoCode={content}
                onSubmit={handleUploadQuestions}
                onHide={() => setQuestionsBuilderShown(false)}
              />
            )}

        )}

      <NavMenu videoId={videoId} playlistId={playlistId} />
    </ErrorBoundary>
  );

  async function handleDeleteVideo() {
    await request.delete(`${URL}/video?videoId=${videoId}`, auth());
    history.push('/videos');
  }

  async function handleEditVideoPage(params) {
    setOnEdit(false);
    try {
      await request.post(`${URL}/video/edit/page`, params, auth());
      const url = fetchedVideoCodeFromURL(params.url);
      setContentObj({
        ...contentObj,
        title: params.title,
        description: params.description,
        content: url
      });
      editVideoThumbs({
        videoId: Number(params.videoId),
        title: params.title,
        url
      });
    } catch (error) {
      handleError(error, dispatch);
    }
  }

  function handleChangeByUserStatus({ contentId, byUser }) {
    setContentObj({
      ...contentObj,
      byUser: byUser
    });
    changeByUserStatusForThumbs({ videoId: Number(contentId), byUser });
  }

  function handleLikeVideo(likes, videoId) {
    onLikeContent({ likes, type: 'video', contentId: videoId });
    likeVideo({ likes, videoId });
  }

  function numberCorrect() {
    const correctAnswers = questions.map(question => {
      return question.correctChoice;
    });
    let numberCorrect = 0;
    for (let i = 0; i < userAnswers.length; i++) {
      if (userAnswers[i] + 1 === correctAnswers[i]) numberCorrect++;
    }
    return numberCorrect;
  }

  function handleSelectChoice(newAnswer) {
    if (typeof userAnswers[currentSlide] === 'number') {
      return setUserAnswers(
        userAnswers.map((answer, index) => {
          return index === currentSlide ? newAnswer : answer;
        })
      );
    }
    const newAnswers = [...userAnswers];
    newAnswers[currentSlide] = newAnswer;
    setUserAnswers(newAnswers);
  }

  function handleRenderSlides() {
    return questions.map((question, index) => {
      const filteredChoices = question.choices.filter(choice => !!choice);
      let isCurrentSlide = index === currentSlide;
      const listItems = filteredChoices.map((choice, index) => {
        let isSelectedChoice = index === userAnswers[currentSlide];
        return {
          label: choice,
          checked: isCurrentSlide && isSelectedChoice
        };
      });
      return (

          <CheckListGroup
            inputType="radio"
            listItems={listItems}
            onSelect={handleSelectChoice}
            style={{ marginTop: '1.5rem', paddingRight: '1rem' }}
          />

      );
    });
  }

  function handleSetDifficulty({ contentId, difficulty }) {
    onSetDifficulty({ contentId, difficulty });
    setDifficulty({ videoId: Number(contentId), difficulty });
  }

  async function handleUploadQuestions(questions) {
    const data = {
      videoId,
      questions: questions.map(question => {
        const choices = question.choices.filter(choice => {
          return choice.label && !stringIsEmpty(choice.label);
        });
        return {
          videoId,
          title: question.title,
          correctChoice: (() => {
            let correctChoice = 0;
            for (let i = 0; i < choices.length; i++) {
              if (choices[i].checked) correctChoice = i + 1;
            }
            return correctChoice;
          })(),
          choice1: choices[0].label,
          choice2: choices[1].label,
          choice3: choices[2]?.label,
          choice4: choices[3]?.label,
          choice5: choices[4]?.label,
          creator: userId
        };
      })
    };
    try {
      await request.post(`${URL}/video/questions`, data, auth());
      const questions = data.questions.map(question => {
        return {
          title: question.title,
          choices: [
            question.choice1,
            question.choice2,
            question.choice3,
            question.choice4,
            question.choice5
          ],
          correctChoice: question.correctChoice
        };
      });
      setContentObj({
        ...contentObj,
        questions
      });
      setQuestionsBuilderShown(false);
      setCurrentSlide(0);
      setUserAnswers([]);
    } catch (error) {
      handleError(error, dispatch);
    }
  }
}

export default connect(
  state => ({
    authLevel: state.UserReducer.authLevel,
    canEdit: state.UserReducer.canEdit,
    userType: state.UserReducer.userType,
    userId: state.UserReducer.userId
  }),
  dispatch => ({
    dispatch,
    changeByUserStatusForThumbs: params =>
      dispatch(changeByUserStatusForThumbs(params)),
    editVideoThumbs: params => dispatch(editVideoThumbs(params)),
    likeVideo: params => dispatch(likeVideo(params)),
    setDifficulty: params => dispatch(setDifficulty(params))
  })
)(VideoPage);
<commit_msg>minor fix<commit_after>import React, { useEffect, useRef, useState } from 'react';
import { useContentObj } from 'helpers/hooks';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import {
  changeByUserStatusForThumbs,
  editVideoThumbs,
  likeVideo,
  setDifficulty
} from 'redux/actions/VideoActions';
import Carousel from 'components/Carousel';
import Button from 'components/Button';
import Loading from 'components/Loading';
import VideoPlayer from 'components/VideoPlayer';
import NotFound from 'components/NotFound';
import CheckListGroup from 'components/CheckListGroup';
import PageTab from './PageTab';
import Comments from 'components/Comments';
import Details from './Details';
import NavMenu from './NavMenu';
import ResultModal from './Modals/ResultModal';
import QuestionsBuilder from './QuestionsBuilder';
import ConfirmModal from 'components/Modals/ConfirmModal';
import { fetchedVideoCodeFromURL, stringIsEmpty } from 'helpers/stringHelpers';
import queryString from 'query-string';
import ErrorBoundary from 'components/Wrappers/ErrorBoundary';
import Subjects from 'components/Subjects';
import RewardStatus from 'components/RewardStatus';
import request from 'axios';
import { Color, mobileMaxWidth } from 'constants/css';
import { css } from 'emotion';
import {
  auth,
  fetchPlaylistsContaining,
  handleError,
  loadComments,
  loadSubjects
} from 'helpers/requestHelpers';
import URL from 'constants/URL';

VideoPage.propTypes = {
  authLevel: PropTypes.number,
  canEdit: PropTypes.bool,
  changeByUserStatusForThumbs: PropTypes.func.isRequired,
  dispatch: PropTypes.func.isRequired,
  editVideoThumbs: PropTypes.func.isRequired,
  history: PropTypes.object.isRequired,
  likeVideo: PropTypes.func.isRequired,
  location: PropTypes.object.isRequired,
  match: PropTypes.object.isRequired,
  setDifficulty: PropTypes.func.isRequired,
  userId: PropTypes.number
};

function VideoPage({
  authLevel,
  canEdit,
  changeByUserStatusForThumbs,
  dispatch,
  editVideoThumbs,
  history,
  likeVideo,
  location: { search },
  match: {
    params: { videoId: initialVideoId }
  },
  setDifficulty,
  userId
}) {
  const [changingPage, setChangingPage] = useState(false);
  const [watchTabActive, setWatchTabActive] = useState(true);
  const [currentSlide, setCurrentSlide] = useState(0);
  const [userAnswers, setUserAnswers] = useState([]);
  const [resultModalShown, setResultModalShown] = useState(false);
  const [confirmModalShown, setConfirmModalShown] = useState(false);
  const [onEdit, setOnEdit] = useState(false);
  const [questionsBuilderShown, setQuestionsBuilderShown] = useState(false);
  const [videoId, setVideoId] = useState(initialVideoId);
  const [videoLoading, setVideoLoading] = useState(true);
  const [videoUnavailable, setVideoUnavailable] = useState(false);
  const mounted = useRef(true);
  const CommentInputAreaRef = useRef(null);
  const videoIdRef = useRef(initialVideoId);
  const {
    contentObj,
    contentObj: {
      byUser,
      childComments: comments,
      commentsLoadMoreButton,
      content,
      description,
      difficulty,
      hasHqThumb,
      likes,
      questions,
      stars,
      subjects,
      subjectsLoadMoreButton,
      tags,
      timeStamp,
      title,
      uploader,
      views
    },
    setContentObj,
    onAddTags,
    onAttachStar,
    onDeleteComment,
    onDeleteSubject,
    onEditComment,
    onEditRewardComment,
    onEditSubject,
    onInitContent,
    onLikeComment,
    onLikeContent,
    onLoadSubjectComments,
    onLoadMoreComments,
    onLoadMoreReplies,
    onLoadMoreSubjects,
    onLoadMoreSubjectComments,
    onLoadMoreSubjectReplies,
    onSetDifficulty,
    onSetSubjectDifficulty,
    onUploadComment,
    onUploadReply,
    onUploadSubject
  } = useContentObj({
    type: 'video',
    contentId: Number(videoId)
  });

  useEffect(() => {
    mounted.current = true;
    videoIdRef.current = initialVideoId;
    setChangingPage(true);
    loadVideoPage();
    setChangingPage(false);
    async function loadVideoPage() {
      try {
        const { data } = await request.get(
          `${URL}/video/page?videoId=${videoIdRef.current}`
        );
        const subjectsObj = await loadSubjects({
          type: 'video',
          contentId: videoIdRef.current
        });
        const commentsObj = await loadComments({
          id: videoIdRef.current,
          type: 'video'
        });
        const tags = await fetchPlaylistsContaining({
          videoId: videoIdRef.current
        });
        if (mounted.current) {
          setVideoId(initialVideoId);
          setVideoLoading(false);
          onInitContent({
            content: {
              ...data,
              tags,
              contentId: Number(videoIdRef.current),
              childComments: commentsObj?.comments || [],
              commentsLoadMoreButton: commentsObj?.loadMoreButton || false,
              subjects: subjectsObj?.results || [],
              subjectsLoadMoreButton: subjectsObj?.loadMoreButton || false
            }
          });
        }
      } catch (error) {
        if (error.response) {
          const { data = {} } = error.response;
          if (data.notFound) {
            setVideoUnavailable(true);
          }
        }
        console.error(error.response || error);
      }
    }
    return function cleanUp() {
      mounted.current = false;
    };
  }, [initialVideoId]);

  const { playlist: playlistId } = queryString.parse(search);
  const userIsUploader = uploader?.id === userId;
  const userCanEditThis = canEdit && authLevel >= uploader?.authLevel;
  return (
    <ErrorBoundary
      className={css`
        display: flex;
        justify-content: space-between;
        width: 100%;
        height: 100%;
        margin-top: 1rem;
        @media (max-width: ${mobileMaxWidth}) {
          margin-top: 0;
          flex-direction: column;
        }
      `}
    >

        {(videoLoading || videoUnavailable) && (

            {videoLoading && <Loading text="Loading Video..." />}
            {videoUnavailable && <NotFound text="Video does not exist" />}

        )}
        {!videoLoading && !videoUnavailable && content && (

              <PageTab
                questions={questions}
                watchTabActive={watchTabActive}
                onWatchTabClick={() => setWatchTabActive(true)}
                onQuestionTabClick={() => setWatchTabActive(false)}
              />

                {!questionsBuilderShown && (
                  <VideoPlayer
                    difficulty={difficulty}
                    byUser={!!byUser}
                    key={videoId}
                    hasHqThumb={hasHqThumb}
                    onEdit={onEdit}
                    videoId={videoId}
                    videoCode={content}
                    title={title}
                    uploader={uploader}
                    minimized={!watchTabActive}
                  />
                )}
                {!watchTabActive && questions.length > 0 && (
                  <Carousel
                    allowDrag={false}
                    style={{ marginTop: !!difficulty && '1rem' }}
                    progressBar
                    showQuestionsBuilder={() => setQuestionsBuilderShown(true)}
                    userIsUploader={userIsUploader}
                    userCanEditThis={userCanEditThis}
                    slidesToShow={1}
                    slidesToScroll={1}
                    slideIndex={currentSlide}
                    afterSlide={setCurrentSlide}
                    onFinish={() => setResultModalShown(true)}
                  >
                    {handleRenderSlides()}
                  </Carousel>
                )}
                {!watchTabActive && questions.length === 0 && (

There are no questions yet.

                    {(userIsUploader || userCanEditThis) && (
                      <Button
                        style={{ marginTop: '2rem', fontSize: '2rem' }}
                        success
                        filled
                        onClick={() => setQuestionsBuilderShown(true)}
                      >
                        Add Questions
                      </Button>
                    )}

                )}

               setOnEdit(true)}
                onEditCancel={() => setOnEdit(false)}
                onEditFinish={handleEditVideoPage}
                onDelete={() => setConfirmModalShown(true)}
                setDifficulty={handleSetDifficulty}
                stars={stars}
                tags={tags}
                title={title}
                timeStamp={timeStamp}
                uploader={uploader}
                userId={userId}
                videoId={videoId}
                videoViews={views}
              />
              <RewardStatus
                contentType="video"
                difficulty={byUser ? 5 : 0}
                onCommentEdit={onEditRewardComment}
                style={{
                  fontSize: '1.4rem'
                }}
                stars={stars}
                uploaderName={uploader.username}
              />

            <Subjects
              loadMoreButton={subjectsLoadMoreButton}
              subjects={subjects}
              onLoadMoreSubjects={onLoadMoreSubjects}
              onLoadSubjectComments={onLoadSubjectComments}
              onSubjectEditDone={onEditSubject}
              onSubjectDelete={onDeleteSubject}
              setSubjectDifficulty={onSetSubjectDifficulty}
              uploadSubject={onUploadSubject}
              contentId={videoId}
              type="video"
              rootDifficulty={difficulty}
              commentActions={{
                attachStar: onAttachStar,
                editRewardComment: onEditRewardComment,
                onDelete: onDeleteComment,
                onEditDone: onEditComment,
                onLikeClick: onLikeComment,
                onLoadMoreComments: onLoadMoreSubjectComments,
                onLoadMoreReplies: onLoadMoreSubjectReplies,
                onUploadComment,
                onUploadReply
              }}
            />

                Comment on this video

              <Comments
                autoExpand
                comments={comments}
                inputAreaInnerRef={CommentInputAreaRef}
                inputTypeLabel={'comment'}
                loadMoreButton={commentsLoadMoreButton}
                onAttachStar={onAttachStar}
                onCommentSubmit={onUploadComment}
                onDelete={onDeleteComment}
                onEditDone={onEditComment}
                onLikeClick={onLikeComment}
                onLoadMoreComments={onLoadMoreComments}
                onLoadMoreReplies={onLoadMoreReplies}
                onReplySubmit={onUploadReply}
                onRewardCommentEdit={onEditRewardComment}
                parent={{
                  type: 'video',
                  difficulty,
                  id: Number(videoId),
                  uploader
                }}
                style={{ paddingTop: '1rem' }}
                userId={userId}
              />

            {resultModalShown && (
              <ResultModal
                onHide={() => setResultModalShown(false)}
                numberCorrect={numberCorrect}
                totalQuestions={questions.length}
              />
            )}
            {confirmModalShown && (
              <ConfirmModal
                title="Remove Video"
                onHide={() => setConfirmModalShown(false)}
                onConfirm={handleDeleteVideo}
              />
            )}
            {questionsBuilderShown && (
              <QuestionsBuilder
                questions={questions}
                title={title}
                videoCode={content}
                onSubmit={handleUploadQuestions}
                onHide={() => setQuestionsBuilderShown(false)}
              />
            )}

        )}

      <NavMenu videoId={videoId} playlistId={playlistId} />
    </ErrorBoundary>
  );

  async function handleDeleteVideo() {
    await request.delete(`${URL}/video?videoId=${videoId}`, auth());
    history.push('/videos');
  }

  async function handleEditVideoPage(params) {
    setOnEdit(false);
    try {
      await request.post(`${URL}/video/edit/page`, params, auth());
      const url = fetchedVideoCodeFromURL(params.url);
      setContentObj({
        ...contentObj,
        title: params.title,
        description: params.description,
        content: url
      });
      editVideoThumbs({
        videoId: Number(params.videoId),
        title: params.title,
        url
      });
    } catch (error) {
      handleError(error, dispatch);
    }
  }

  function handleChangeByUserStatus({ contentId, byUser }) {
    setContentObj({
      ...contentObj,
      byUser: byUser
    });
    changeByUserStatusForThumbs({ videoId: Number(contentId), byUser });
  }

  function handleLikeVideo(likes, videoId) {
    onLikeContent({ likes, type: 'video', contentId: videoId });
    likeVideo({ likes, videoId });
  }

  function numberCorrect() {
    const correctAnswers = questions.map(question => {
      return question.correctChoice;
    });
    let numberCorrect = 0;
    for (let i = 0; i < userAnswers.length; i++) {
      if (userAnswers[i] + 1 === correctAnswers[i]) numberCorrect++;
    }
    return numberCorrect;
  }

  function handleSelectChoice(newAnswer) {
    if (typeof userAnswers[currentSlide] === 'number') {
      return setUserAnswers(
        userAnswers.map((answer, index) => {
          return index === currentSlide ? newAnswer : answer;
        })
      );
    }
    const newAnswers = [...userAnswers];
    newAnswers[currentSlide] = newAnswer;
    setUserAnswers(newAnswers);
  }

  function handleRenderSlides() {
    return questions.map((question, index) => {
      const filteredChoices = question.choices.filter(choice => !!choice);
      let isCurrentSlide = index === currentSlide;
      const listItems = filteredChoices.map((choice, index) => {
        let isSelectedChoice = index === userAnswers[currentSlide];
        return {
          label: choice,
          checked: isCurrentSlide && isSelectedChoice
        };
      });
      return (

          <CheckListGroup
            inputType="radio"
            listItems={listItems}
            onSelect={handleSelectChoice}
            style={{ marginTop: '1.5rem', paddingRight: '1rem' }}
          />

      );
    });
  }

  function handleSetDifficulty({ contentId, difficulty }) {
    onSetDifficulty({ contentId, difficulty });
    setDifficulty({ videoId: Number(contentId), difficulty });
  }

  async function handleUploadQuestions(questions) {
    const data = {
      videoId,
      questions: questions.map(question => {
        const choices = question.choices.filter(choice => {
          return choice.label && !stringIsEmpty(choice.label);
        });
        return {
          videoId,
          title: question.title,
          correctChoice: (() => {
            let correctChoice = 0;
            for (let i = 0; i < choices.length; i++) {
              if (choices[i].checked) correctChoice = i + 1;
            }
            return correctChoice;
          })(),
          choice1: choices[0].label,
          choice2: choices[1].label,
          choice3: choices[2]?.label,
          choice4: choices[3]?.label,
          choice5: choices[4]?.label,
          creator: userId
        };
      })
    };
    try {
      await request.post(`${URL}/video/questions`, data, auth());
      const questions = data.questions.map(question => {
        return {
          title: question.title,
          choices: [
            question.choice1,
            question.choice2,
            question.choice3,
            question.choice4,
            question.choice5
          ],
          correctChoice: question.correctChoice
        };
      });
      setContentObj({
        ...contentObj,
        questions
      });
      setQuestionsBuilderShown(false);
      setCurrentSlide(0);
      setUserAnswers([]);
    } catch (error) {
      handleError(error, dispatch);
    }
  }
}

export default connect(
  state => ({
    authLevel: state.UserReducer.authLevel,
    canEdit: state.UserReducer.canEdit,
    userType: state.UserReducer.userType,
    userId: state.UserReducer.userId
  }),
  dispatch => ({
    dispatch,
    changeByUserStatusForThumbs: params =>
      dispatch(changeByUserStatusForThumbs(params)),
    editVideoThumbs: params => dispatch(editVideoThumbs(params)),
    likeVideo: params => dispatch(likeVideo(params)),
    setDifficulty: params => dispatch(setDifficulty(params))
  })
)(VideoPage);
<|endoftext|>


<commit_before>#!/usr/bin/env node
'use strict';

var npmOpen = require('./');

var usage = function () {
  console.log('Usage:  npm-open [--npm] [<directory>]');
  return 1;
};

var openProject = function (directory, npm) {
  (npm ? npmOpen.npm : npmOpen)(directory);
  return 0;
};

var openCurrentProject = function (npm) {
  return openProject(process.cwd(), npm);
};

process.exitCode = (function (argv) {
  var npm;
  if (argv[0] == '--npm') {
    npm = true;
    argv.shift();
  }

  switch (argv.length) {
    case 0: return openCurrentProject(npm);
    case 1: return openProject(argv[0], npm);
    default: return usage();
  }
}(process.argv.slice(2)));
<commit_msg>Add --help<commit_after>#!/usr/bin/env node
'use strict';

var npmOpen = require('./');

var usage = function () {
  console.log('Usage:  npm-open [--npm] [<directory>]');
  return 1;
};

var openProject = function (directory, npm) {
  (npm ? npmOpen.npm : npmOpen)(directory);
  return 0;
};

var openCurrentProject = function (npm) {
  return openProject(process.cwd(), npm);
};

process.exitCode = (function (argv) {
  if (argv[0] == '--help') {
    return usage();
  }

  var npm;
  if (argv[0] == '--npm') {
    npm = true;
    argv.shift();
  }

  switch (argv.length) {
    case 0: return openCurrentProject(npm);
    case 1: return openProject(argv[0], npm);
    default: return usage();
  }
}(process.argv.slice(2)));
<|endoftext|>