<commit_before>import React, { useState, useEffect } from 'react';
import { c } from 'ttag';
import { querySessions, revokeOtherSessions, revokeSession } from 'proton-shared/lib/api/auth';
import { ELEMENTS_PER_PAGE } from 'proton-shared/lib/constants';
import {
    Button,
    Table,
    TableHeader,
    TableBody,
    TableRow,
    Time,
    Pagination,
    Alert,
    Block,
    ConfirmModal,
    usePagination,
} from '../../components';
import { useApi, useAuthentication, useModals, useLoading, useNotifications } from '../../hooks';

import SessionAction from './SessionAction';

const getClientsI18N = () => ({
    Web: c('Badge').t`ProtonMail for web`,
    VPN: c('Badge').t`ProtonVPN for Windows`,
    WebVPN: c('Badge').t`ProtonVPN for web`,
    Admin: c('Badge').t`Admin`,
    ImportExport: c('Badge').t`ProtonMail Import-Export`,
    Bridge: c('Badge').t`ProtonMail Bridge`,
    iOS: c('Badge').t`ProtonMail for iOS`,
    Android: c('Badge').t`ProtonMail for Android`,
    WebMail: c('Badge').t`ProtonMail for web`,
    WebMailSettings: c('Badge').t`ProtonMail settings for web`,
    WebContacts: c('Badge').t`ProtonContacts for web`,
    WebVPNSettings: c('Badge').t`ProtonVPN settings for web`,
    WebCalendar: c('Badge').t`ProtonCalendar for web`,
    WebDrive: c('Badge').t`ProtonDrive for web`,
    // WebWallet: c('Badge').t`ProtonWallet for web`,
    WebAdmin: c('Badge').t`Admin`,
    iOSMail: c('Badge').t`ProtonMail for iOS`,
    iOSVPN: c('Badge').t`ProtonVPN for iOS`,
    iOSCalendar: c('Badge').t`ProtonCalendar for iOS`,
    AndroidMail: c('Badge').t`ProtonMail for Android`,
    AndroidVPN: c('Badge').t`ProtonVPN for Android`,
    AndroidCalendar: c('Badge').t`ProtonCalendar for Android`,
    WindowsVPN: c('Badge').t`ProtonVPN for Windows`,
    WindowsImportExport: c('Badge').t`ProtonMail Import-Export for Windows`,
    WindowsBridge: c('Badge').t`ProtonMail Bridge for Windows`,
    macOSVPN: c('Badge').t`ProtonVPN for macOS`,
    macOSImportExport: c('Badge').t`ProtonMail Import-Export for macOS`,
    macOSBridge: c('Badge').t`ProtonMail Bridge for macOS`,
    LinuxImportExport: c('Badge').t`ProtonMail Import-Export for GNU/Linux`,
    LinuxBridge: c('Badge').t`ProtonMail Bridge for GNU/Linux`,
});

const SessionsSection = () => {
    const { createNotification } = useNotifications();
    const api = useApi();
    const authentication = useAuthentication();
    const [loading, withLoading] = useLoading();
    const [table, setTable] = useState({ sessions: [], total: 0 });
    const { page, list, onNext, onPrevious, onSelect } = usePagination(table.sessions);
    const { createModal } = useModals();

    const fetchSessions = async () => {
        const { Sessions } = await api(querySessions());
        setTable({ sessions: Sessions.reverse(), total: Sessions.length }); // Most recent, first
    };

    const handleRevoke = async (UID) => {
        await api(UID ? revokeSession(UID) : revokeOtherSessions());
        await fetchSessions();
        createNotification({ text: UID ? c('Success').t`Session revoked` : c('Success').t`Sessions revoked` });
    };

    const handleOpenModal = () => {
        createModal(
            <ConfirmModal onConfirm={() => withLoading(handleRevoke())}>
                <Alert>{c('Info').t`Do you want to revoke all other sessions than the current one?`}</Alert>
            </ConfirmModal>
        );
    };

    useEffect(() => {
        withLoading(fetchSessions());
    }, []);

    const i18n = getClientsI18N();
    const currentUID = authentication.getUID();

    return (
        <>
            <Alert learnMore="https://protonmail.com/support/knowledge-base/log-out-all-other-sessions/">{c('Info')
                .t`Unless you explicitly sign out or change your password, sessions remain active and only expire after 30 days of inactivity.`}</Alert>
            <Block className="flex flex-spacebetween">

                    <Button onClick={handleOpenModal}>{c('Action').t`Revoke all other sessions`}</Button>

                <Pagination
                    page={page}
                    total={table.total}
                    limit={ELEMENTS_PER_PAGE}
                    onNext={onNext}
                    onPrevious={onPrevious}
                    onSelect={onSelect}
                />
            </Block>

                <TableHeader cells={[c('Title').t`App`, c('Title').t`Date`, c('Title').t`Action`]} />
                <TableBody loading={loading} colSpan={3}>
                    {list.map((session) => {
                        const key = session.UID;
                        return (
                            <TableRow
                                key={key}
                                cells={[
                                    i18n[session.ClientID],
                                    <Time format="PPp" key={1}>
                                        {session.CreateTime}
                                    </Time>,
                                    <SessionAction
                                        key={2}
                                        session={session}
                                        currentUID={currentUID}
                                        onRevoke={
                                            session.Revocable ? () => withLoading(handleRevoke(session.UID)) : undefined
                                        }
                                    />,
                                ]}
                            />
                        );
                    })}
                </TableBody>

        </>
    );
};

export default SessionsSection;
<commit_msg>Add proton account for web in session section<commit_after>import React, { useState, useEffect } from 'react';
import { c } from 'ttag';
import { querySessions, revokeOtherSessions, revokeSession } from 'proton-shared/lib/api/auth';
import { ELEMENTS_PER_PAGE } from 'proton-shared/lib/constants';
import {
    Button,
    Table,
    TableHeader,
    TableBody,
    TableRow,
    Time,
    Pagination,
    Alert,
    Block,
    ConfirmModal,
    usePagination,
} from '../../components';
import { useApi, useAuthentication, useModals, useLoading, useNotifications } from '../../hooks';

import SessionAction from './SessionAction';

const getClientsI18N = () => ({
    Web: c('Badge').t`ProtonMail for web`,
    VPN: c('Badge').t`ProtonVPN for Windows`,
    WebVPN: c('Badge').t`ProtonVPN for web`,
    Admin: c('Badge').t`Admin`,
    ImportExport: c('Badge').t`ProtonMail Import-Export`,
    Bridge: c('Badge').t`ProtonMail Bridge`,
    iOS: c('Badge').t`ProtonMail for iOS`,
    Android: c('Badge').t`ProtonMail for Android`,
    WebAccount: c('Badge').t`Proton Account for web`,
    WebMail: c('Badge').t`ProtonMail for web`,
    WebMailSettings: c('Badge').t`ProtonMail settings for web`,
    WebContacts: c('Badge').t`ProtonContacts for web`,
    WebVPNSettings: c('Badge').t`ProtonVPN settings for web`,
    WebCalendar: c('Badge').t`ProtonCalendar for web`,
    WebDrive: c('Badge').t`ProtonDrive for web`,
    // WebWallet: c('Badge').t`ProtonWallet for web`,
    WebAdmin: c('Badge').t`Admin`,
    iOSMail: c('Badge').t`ProtonMail for iOS`,
    iOSVPN: c('Badge').t`ProtonVPN for iOS`,
    iOSCalendar: c('Badge').t`ProtonCalendar for iOS`,
    AndroidMail: c('Badge').t`ProtonMail for Android`,
    AndroidVPN: c('Badge').t`ProtonVPN for Android`,
    AndroidCalendar: c('Badge').t`ProtonCalendar for Android`,
    WindowsVPN: c('Badge').t`ProtonVPN for Windows`,
    WindowsImportExport: c('Badge').t`ProtonMail Import-Export for Windows`,
    WindowsBridge: c('Badge').t`ProtonMail Bridge for Windows`,
    macOSVPN: c('Badge').t`ProtonVPN for macOS`,
    macOSImportExport: c('Badge').t`ProtonMail Import-Export for macOS`,
    macOSBridge: c('Badge').t`ProtonMail Bridge for macOS`,
    LinuxImportExport: c('Badge').t`ProtonMail Import-Export for GNU/Linux`,
    LinuxBridge: c('Badge').t`ProtonMail Bridge for GNU/Linux`,
});

const SessionsSection = () => {
    const { createNotification } = useNotifications();
    const api = useApi();
    const authentication = useAuthentication();
    const [loading, withLoading] = useLoading();
    const [table, setTable] = useState({ sessions: [], total: 0 });
    const { page, list, onNext, onPrevious, onSelect } = usePagination(table.sessions);
    const { createModal } = useModals();

    const fetchSessions = async () => {
        const { Sessions } = await api(querySessions());
        setTable({ sessions: Sessions.reverse(), total: Sessions.length }); // Most recent, first
    };

    const handleRevoke = async (UID) => {
        await api(UID ? revokeSession(UID) : revokeOtherSessions());
        await fetchSessions();
        createNotification({ text: UID ? c('Success').t`Session revoked` : c('Success').t`Sessions revoked` });
    };

    const handleOpenModal = () => {
        createModal(
            <ConfirmModal onConfirm={() => withLoading(handleRevoke())}>
                <Alert>{c('Info').t`Do you want to revoke all other sessions than the current one?`}</Alert>
            </ConfirmModal>
        );
    };

    useEffect(() => {
        withLoading(fetchSessions());
    }, []);

    const i18n = getClientsI18N();
    const currentUID = authentication.getUID();

    return (
        <>
            <Alert learnMore="https://protonmail.com/support/knowledge-base/log-out-all-other-sessions/">{c('Info')
                .t`Unless you explicitly sign out or change your password, sessions remain active and only expire after 30 days of inactivity.`}</Alert>
            <Block className="flex flex-spacebetween">

                    <Button onClick={handleOpenModal}>{c('Action').t`Revoke all other sessions`}</Button>

                <Pagination
                    page={page}
                    total={table.total}
                    limit={ELEMENTS_PER_PAGE}
                    onNext={onNext}
                    onPrevious={onPrevious}
                    onSelect={onSelect}
                />
            </Block>

                <TableHeader cells={[c('Title').t`App`, c('Title').t`Date`, c('Title').t`Action`]} />
                <TableBody loading={loading} colSpan={3}>
                    {list.map((session) => {
                        const key = session.UID;
                        return (
                            <TableRow
                                key={key}
                                cells={[
                                    i18n[session.ClientID],
                                    <Time format="PPp" key={1}>
                                        {session.CreateTime}
                                    </Time>,
                                    <SessionAction
                                        key={2}
                                        session={session}
                                        currentUID={currentUID}
                                        onRevoke={
                                            session.Revocable ? () => withLoading(handleRevoke(session.UID)) : undefined
                                        }
                                    />,
                                ]}
                            />
                        );
                    })}
                </TableBody>

        </>
    );
};

export default SessionsSection;
<|endoftext|>


<commit_before>/**
 * Interaction for the pages module
 */
/* global pageID, CKEDITOR, extrasById, extrasData, ss, templates, initDefaults, selectedId */

jsBackend.pages = {
  // init, something like a constructor
  init: function () {
    // load the tree
    jsBackend.pages.tree.init()

    // are we adding or editing?
    if ($('[data-role="template-switcher"]').length > 0) {
      // load stuff for editing and adding an page
      jsBackend.pages.extras.init()
      jsBackend.pages.template.init()
    }

    if ($('[data-role="move-page-toggle"]').length > 0) {
      jsBackend.pages.move.init()
    }
  }
}

jsBackend.pages.move = {
  init: function () {
    jsBackend.pages.move.allowGodUsersToEnableTheMovePageSetting()
    jsBackend.pages.move.handleChangingPageTree()
    jsBackend.pages.move.handleChangingReferencePage()
  },

  handleChangingReferencePage: function () {
    $('[data-role="move-page-pages-select"]').on('change', function () {
      var $this = $(this)
      var $selectedOption = $this.find(':selected')
      var $typeSelect = $('[data-role="move-page-type-changer"]')
      var $typeSelectOptions = $typeSelect.find('option')
      var selectedValue = $typeSelect.find('option:selected').val()
      $typeSelectOptions.removeClass('disabled').prop('disabled', false).prop('selected', false)

      if ($selectedOption.data('allowInside') !== 1 && $this.val() !== '0') {
        $typeSelect.find('[value=inside]').addClass('disabled').prop('disabled', true)
      }
      if ($selectedOption.data('allowBefore') !== 1) {
        $typeSelect.find('[value=before]').addClass('disabled').prop('disabled', true)
      }
      if ($selectedOption.data('allowAfter') !== 1) {
        $typeSelect.find('[value=after]').addClass('disabled').prop('disabled', true)
      }

      if ($typeSelect.find('option[value="' + selectedValue + '"]').is(':disabled')) {
        selectedValue = $typeSelectOptions.not(':disabled').first().val()
      }

      $typeSelect.val(selectedValue)
      $typeSelect.trigger('change')
    }).trigger('change')
  },

  handleChangingPageTree: function () {
    $('[data-role="move-page-tree-changer"]').on('change', function () {
      var $this = $(this)
      var $pagesSelect = $('[data-role="move-page-pages-select"]')
      var selectedValue = $pagesSelect.find('option:selected').val()

      $pagesSelect.find('optgroup option').prop('selected', false).prop('disabled', true)

      // only show the pages of the selected tree
      $pagesSelect.find('optgroup').hide().children().hide()
      var $visiblePages = $pagesSelect.find('[data-tree-name="' + $this.val() + '"]')
      if ($visiblePages.length > 0) {
        $visiblePages.show().prop('disabled', false)
        $pagesSelect.find('optgroup[label="' + $visiblePages.first().attr('data-tree-label') + '"]').show()
      }

      if ($pagesSelect.find('option[value="' + selectedValue + '"]').is(':disabled')) {
        selectedValue = 0
      }

      $pagesSelect.val(selectedValue)
      $pagesSelect.trigger('change')
    }).trigger('change')
  },

  allowGodUsersToEnableTheMovePageSetting: function () {
    var $toggle = $('[data-role="allow-move-toggle"]')
    if ($toggle.length === 0) {
      return
    }

    $toggle.on('change', function () {
      var $movePageToggle = $('[data-role="move-page-toggle"]')
      if ($(this).is(':checked')) {
        $movePageToggle.removeClass('disabled').prop('disabled', false)

        return
      }

      $movePageToggle.addClass('disabled').prop('disabled', true).prop('checked', false).trigger('change')
    }).trigger('change')
  }
}

/**
 * All methods related to the controls (buttons, ...)
 */
jsBackend.pages.extras = {
  // init, something like a constructor
  init: function () {
    jsBackend.pages.extras.blockTypeSwitcher()
    jsBackend.pages.extras.saveBlock()
    jsBackend.pages.extras.newBlock()

    // bind events
    $(document).on('click', '[data-role="page-block-visibility"]', jsBackend.pages.extras.toggleVisibilityBlock)
  },

  newBlock: function () {
    $('[data-role="page-content-tab"]').on('collection-field-added', '[data-addfield="collection"]', function (event, formCollectionItem) {
      var $formCollectionItem = $(formCollectionItem)
      $formCollectionItem.find('[data-role="select-block-type"]').trigger('change')
      $formCollectionItem.find('[data-role="page-block-edit"]').trigger('click')
    })
  },

  saveBlock: function () {
    $('[data-role="page-content-tab"]').on('click', '[data-role="page-block-save"]', function () {
      var $modal = $(this).closest('.modal')
      var separator = ' › '
      var $pageBlockTitle = $modal.closest('[data-role="page-block-wrapper"]').find('[data-role="page-block-title"]')
      var selectedBlockType = $modal.find('[data-role="select-block-type"]').val()
      var title = $modal.find('[data-role="select-block-type"] option:selected').text()

      if (selectedBlockType === 'block' || selectedBlockType === 'widget') {
        title += separator + jsBackend.pages.extras.extractExtraTitle(
          separator,
          $modal.find('[data-role="page-block-content-type-wrapper"][data-type="' + selectedBlockType + '"] select option:selected')
        )
      }

      $pageBlockTitle.text(title)
      $modal.modal('hide')
    })

    $('[data-role="page-content-tab"] [data-role="page-block-save"]').trigger('click')
  },

  extractExtraTitle: function (separator, $selectedOption) {
    return $selectedOption.closest('optgroup').attr('label') + separator + $selectedOption.text()
  },

  blockTypeSwitcher: function () {
    $('[data-role="page-content-tab"]').on('change', '[data-role="select-block-type"]', function () {
      var $this = $(this)
      var $wrapper = $this.closest('[data-role="page-block-form-wrapper"]')
      $wrapper.find('[data-role="page-block-content-type-wrapper"]').hide()
      $wrapper.find('[data-role="page-block-content-type-wrapper"][data-type=' + $this.val() + ']').show()

      jsBackend.pages.extras.validateOnlyOneModuleIsConnected()
    })
    $('[data-role="page-content-tab"] [data-role="select-block-type"]').trigger('change') // set the initial state
  },

  validateOnlyOneModuleIsConnected: function () {
    var $selectedBlockOption = $('[data-role="select-block-type"] option:selected[value="block"]:first')
    var $allBlockOptions = $('[data-role="select-block-type"] option[value="block"]')

    if ($selectedBlockOption.length === 0) {
      $allBlockOptions.removeAttr('disabled')

      return
    }

    $allBlockOptions.not($selectedBlockOption).attr('disabled', 'disabled')
  },

  toggleVisibilityBlock: function (e) {
    // get checkbox
    var $checkbox = $(e.currentTarget)
    var $blackWrapper = $checkbox.parents('[data-role="page-block-wrapper"]')
    var $label = $blackWrapper.find('label[for="' + $checkbox.attr('id') + '"]')

    if ($blackWrapper.hasClass('block-not-visible')) {
      // make visible
      $blackWrapper.removeClass('block-not-visible')
      $label.find('[data-fa-i2svg]').removeClass('fa-eye-slash').addClass('fa-eye')
      $label.find('.sr-only').html(jsBackend.locale.lbl('Hide'))
    } else {
      // make invisible
      $blackWrapper.addClass('block-not-visible')
      $label.find('[data-fa-i2svg]').removeClass('fa-eye').addClass('fa-eye-slash')
      $label.find('.sr-only').html(jsBackend.locale.lbl('Show'))
    }
  }
}

/**
 * Template switcher
 */
jsBackend.pages.template = {
  init: function () {
    $('[data-role="template-switcher"]').on('change', function() {
      window.location.replace(jsBackend.pages.template.getChangeTemplateUrl('template-id', $(this).val()))
    })
  },

  getChangeTemplateUrl: function (key, value, url) {
    if (!url) url = jsBackend.pages.template.getChangeTemplateUrl('report', null, window.location.href);
    var re = new RegExp("([?&])" + key + "=.*?(&|#|$)(.*)", "gi"),
      hash

    if (re.test(url)) {
      if (typeof value !== 'undefined' && value !== null) {
        return url.replace(re, '$1' + key + "=" + value + '$2$3')
      }

      hash = url.split('#')
      url = hash[0].replace(re, '$1$3').replace(/(&|\?)$/, '')
      if (typeof hash[1] !== 'undefined' && hash[1] !== null) {
        url += '#' + hash[1]
      }
      return url
    }
    if (typeof value !== 'undefined' && value !== null) {
      var separator = url.indexOf('?') !== -1 ? '&' : '?'
      hash = url.split('#')
      url = hash[0] + separator + key + '=' + value
      if (typeof hash[1] !== 'undefined' && hash[1] !== null) {
        url += '#' + hash[1]
      }
      return url
    }

    return url
  }
}

/**
 * All methods related to the pages tree
 */
jsBackend.pages.tree = {
  // init, something like a constructor
  init: function () {
    if ($('#tree').find('> [data-tree]').length === 0) return false

    var openedIds = []
    if (typeof pageID !== 'undefined') {
      // get parents
      var parents = $('#page-' + pageID).parents('li')

      // init var
      openedIds = ['page-' + pageID]

      // add parents
      for (var i = 0; i < parents.length; i++) {
        openedIds.push($(parents[i]).prop('id'))
      }
    }

    // add home if needed
    if (!utils.array.inArray('page-1', openedIds)) openedIds.push('page-1')

    // jsTree options
    var options = {
      core: {
        animation: 0,
        themes: {
          name: 'proton',
          responsive: true
        },
        multiple: false,
        check_callback: jsBackend.pages.tree.checkCallback
      },
      types: {
        default: {
          icon: 'far fa-file'
        },
        home: {
          icon: 'fas fa-home'
        },
        anchor: {
          icon: 'far fa-file'
        },
        sitemap: {
          icon: 'fas fa-sitemap'
        },
        error: {
          icon: 'fas fa-exclamation-triangle'
        }
      },
      search: {
        show_only_matches: true
      },
      dnd: {
        inside_pos: 'last'
      },
      plugins: ['dnd', 'types', 'state', 'search']
    }

    // Init page tree
    var jsTreeInstance = $('#tree').find('> [data-tree]').jstree(options)

    jsTreeInstance.on('move_node.jstree', function (event, data) {
      jsBackend.pages.tree.onMove(event, data, jsTreeInstance)
    })
    jsTreeInstance.on('copy_node.jstree', function (event, data) {
      jsBackend.pages.tree.onMove(event, data, jsTreeInstance)
    })

    // Search through pages
    var searchThroughPages = function () {
      var v = $('.js-tree-search').val()
      $('#tree').find('> [data-tree]').each(function () {
        $(this).jstree(true).search(v)
      })
    }
    $('.js-tree-search').bind('keyup input', utils.events.debounce(searchThroughPages, 150))

    // To prevent FOUC, we only show the jsTree when it's done loading.
    jsTreeInstance.on('ready.jstree', function (e, data) {
      $('#tree').show()
    })

    // On selecting a node in the tree, visit the anchor.
    jsTreeInstance.on('select_node.jstree', function (e, data, jsTreeInstance) {
      if (data && data.node && data.event) {
        // Get current and new URL
        var node = data.node
        var currentPageURL = window.location.pathname + window.location.search
        var newPageURL = node.a_attr.href

        // Only redirect if destination isn't the current one.
        if (typeof newPageURL !== 'undefined' && newPageURL !== currentPageURL) {
          window.location = newPageURL
        }
      }
    })

    // set the item selected
    if (typeof selectedId !== 'undefined') $('#' + selectedId).addClass('selected')

    jsBackend.pages.tree.toggleJsTreeCollapse(jsTreeInstance)
  },

  checkCallback: function (operation, node, parent) {
    if (operation !== 'move_node') {
      return true
    }

    if (!node.data.allowMove) {
      return false
    }

    if (typeof parent.data === 'undefined' || parent.data === null) {
      return typeof parent.children === 'undefined' || parent.children[0] !== 'page-1'
    }

    return parent.data.allowChildren
  },

  // when an item is moved
  onMove: function (event, data, jsTreeInstance) {
    var tree = data.new_instance.element.first().data('tree')

    var node = event.type === 'copy_node' ? data.original : data.node
    // get pageID that has to be moved
    var currentPageID = node.id.replace('page-', '')

    // set the default type of moving
    var type = 'before'

    // get the position where the page is dropped
    var droppedOnElement = null
    if (data.new_instance._model.data[data.parent].children[data.position + 1] !== undefined) {
      droppedOnElement = $('#' + data.new_instance._model.data[data.parent].children[data.position + 1])
    }

    // when node is dropped on the last position, we can not add it before the last one -> we need after
    if (data.new_instance._model.data[data.parent].children[data.position + 1] === undefined
      && data.new_instance._model.data[data.parent].children[data.position - 1] !== undefined) {
      droppedOnElement = $('#' + data.new_instance._model.data[data.parent].children[data.position - 1])
      type = 'after'
    }

    // when node is dropped on another node to move it inside
    if (data.new_instance._model.data[data.parent].children.length === 1 && data.new_instance._model.data[data.parent].children[0] === node.id) {
      type = 'inside'
      if (data.parent !== undefined && data.parent !== '#') {
        droppedOnElement = $('#' + data.parent)
      }
    }

    // get pageID wheron the page has been dropped
    var droppedOnPageID = 0
    if (droppedOnElement !== null) {
      droppedOnPageID = droppedOnElement.prop('id').replace('page-', '')
    }

    // move the page
    $.ajax({
      data: {
        fork: {action: 'Move'},
        id: currentPageID,
        dropped_on: droppedOnPageID,
        type: type,
        tree: tree
      },
      success: function (json, textStatus) {
        if (json.code !== 200) {
          if (jsBackend.debug) window.alert(textStatus)

          // show message
          jsBackend.messages.add('danger', jsBackend.locale.err('CantBeMoved'))

          // rollback
          jsTreeInstance.jstree('refresh')

          return
        }

        // show message
        jsBackend.messages.add('success', jsBackend.locale.msg('PageIsMoved').replace('%1$s', json.data.title))
      },
      error: function (XMLHttpRequest, textStatus, errorThrown) {
        window.location.reload()
      },
    })
  },

  toggleJsTreeCollapse: function (jsTreeInstance) {
    $('[data-role="toggle-js-tree-collapse"]').on('click', function () {
      var $this = $(this)
      $this.toggleClass('tree-collapsed')
      var collapsed = $this.hasClass('tree-collapsed')
      var $buttonText = $('[data-role="toggle-js-tree-collapse-text"]')

      if (collapsed) {
        $buttonText.html(jsBackend.locale.lbl('OpenTreeNavigation'))
        jsTreeInstance.jstree('close_all')

        return
      }

      $buttonText.html(jsBackend.locale.lbl('CloseTreeNavigation'))
      jsTreeInstance.jstree('open_all')
    })
  }
}

$(jsBackend.pages.init)
<commit_msg>Fix page block eye<commit_after>/**
 * Interaction for the pages module
 */
/* global pageID, CKEDITOR, extrasById, extrasData, ss, templates, initDefaults, selectedId */

jsBackend.pages = {
  // init, something like a constructor
  init: function () {
    // load the tree
    jsBackend.pages.tree.init()

    // are we adding or editing?
    if ($('[data-role="template-switcher"]').length > 0) {
      // load stuff for editing and adding an page
      jsBackend.pages.extras.init()
      jsBackend.pages.template.init()
    }

    if ($('[data-role="move-page-toggle"]').length > 0) {
      jsBackend.pages.move.init()
    }
  }
}

jsBackend.pages.move = {
  init: function () {
    jsBackend.pages.move.allowGodUsersToEnableTheMovePageSetting()
    jsBackend.pages.move.handleChangingPageTree()
    jsBackend.pages.move.handleChangingReferencePage()
  },

  handleChangingReferencePage: function () {
    $('[data-role="move-page-pages-select"]').on('change', function () {
      var $this = $(this)
      var $selectedOption = $this.find(':selected')
      var $typeSelect = $('[data-role="move-page-type-changer"]')
      var $typeSelectOptions = $typeSelect.find('option')
      var selectedValue = $typeSelect.find('option:selected').val()
      $typeSelectOptions.removeClass('disabled').prop('disabled', false).prop('selected', false)

      if ($selectedOption.data('allowInside') !== 1 && $this.val() !== '0') {
        $typeSelect.find('[value=inside]').addClass('disabled').prop('disabled', true)
      }
      if ($selectedOption.data('allowBefore') !== 1) {
        $typeSelect.find('[value=before]').addClass('disabled').prop('disabled', true)
      }
      if ($selectedOption.data('allowAfter') !== 1) {
        $typeSelect.find('[value=after]').addClass('disabled').prop('disabled', true)
      }

      if ($typeSelect.find('option[value="' + selectedValue + '"]').is(':disabled')) {
        selectedValue = $typeSelectOptions.not(':disabled').first().val()
      }

      $typeSelect.val(selectedValue)
      $typeSelect.trigger('change')
    }).trigger('change')
  },

  handleChangingPageTree: function () {
    $('[data-role="move-page-tree-changer"]').on('change', function () {
      var $this = $(this)
      var $pagesSelect = $('[data-role="move-page-pages-select"]')
      var selectedValue = $pagesSelect.find('option:selected').val()

      $pagesSelect.find('optgroup option').prop('selected', false).prop('disabled', true)

      // only show the pages of the selected tree
      $pagesSelect.find('optgroup').hide().children().hide()
      var $visiblePages = $pagesSelect.find('[data-tree-name="' + $this.val() + '"]')
      if ($visiblePages.length > 0) {
        $visiblePages.show().prop('disabled', false)
        $pagesSelect.find('optgroup[label="' + $visiblePages.first().attr('data-tree-label') + '"]').show()
      }

      if ($pagesSelect.find('option[value="' + selectedValue + '"]').is(':disabled')) {
        selectedValue = 0
      }

      $pagesSelect.val(selectedValue)
      $pagesSelect.trigger('change')
    }).trigger('change')
  },

  allowGodUsersToEnableTheMovePageSetting: function () {
    var $toggle = $('[data-role="allow-move-toggle"]')
    if ($toggle.length === 0) {
      return
    }

    $toggle.on('change', function () {
      var $movePageToggle = $('[data-role="move-page-toggle"]')
      if ($(this).is(':checked')) {
        $movePageToggle.removeClass('disabled').prop('disabled', false)

        return
      }

      $movePageToggle.addClass('disabled').prop('disabled', true).prop('checked', false).trigger('change')
    }).trigger('change')
  }
}

/**
 * All methods related to the controls (buttons, ...)
 */
jsBackend.pages.extras = {
  // init, something like a constructor
  init: function () {
    jsBackend.pages.extras.blockTypeSwitcher()
    jsBackend.pages.extras.saveBlock()
    jsBackend.pages.extras.newBlock()

    // bind events
    $(document).on('click', '[data-role="page-block-visibility"]', jsBackend.pages.extras.toggleVisibilityBlock)
  },

  newBlock: function () {
    $('[data-role="page-content-tab"]').on('collection-field-added', '[data-addfield="collection"]', function (event, formCollectionItem) {
      var $formCollectionItem = $(formCollectionItem)
      $formCollectionItem.find('[data-role="select-block-type"]').trigger('change')
      $formCollectionItem.find('[data-role="page-block-edit"]').trigger('click')
    })
  },

  saveBlock: function () {
    $('[data-role="page-content-tab"]').on('click', '[data-role="page-block-save"]', function () {
      var $modal = $(this).closest('.modal')
      var separator = ' › '
      var $pageBlockTitle = $modal.closest('[data-role="page-block-wrapper"]').find('[data-role="page-block-title"]')
      var selectedBlockType = $modal.find('[data-role="select-block-type"]').val()
      var title = $modal.find('[data-role="select-block-type"] option:selected').text()

      if (selectedBlockType === 'block' || selectedBlockType === 'widget') {
        title += separator + jsBackend.pages.extras.extractExtraTitle(
          separator,
          $modal.find('[data-role="page-block-content-type-wrapper"][data-type="' + selectedBlockType + '"] select option:selected')
        )
      }

      $pageBlockTitle.text(title)
      $modal.modal('hide')
    })

    $('[data-role="page-content-tab"] [data-role="page-block-save"]').trigger('click')
  },

  extractExtraTitle: function (separator, $selectedOption) {
    return $selectedOption.closest('optgroup').attr('label') + separator + $selectedOption.text()
  },

  blockTypeSwitcher: function () {
    $('[data-role="page-content-tab"]').on('change', '[data-role="select-block-type"]', function () {
      var $this = $(this)
      var $wrapper = $this.closest('[data-role="page-block-form-wrapper"]')
      $wrapper.find('[data-role="page-block-content-type-wrapper"]').hide()
      $wrapper.find('[data-role="page-block-content-type-wrapper"][data-type=' + $this.val() + ']').show()

      jsBackend.pages.extras.validateOnlyOneModuleIsConnected()
    })
    $('[data-role="page-content-tab"] [data-role="select-block-type"]').trigger('change') // set the initial state
  },

  validateOnlyOneModuleIsConnected: function () {
    var $selectedBlockOption = $('[data-role="select-block-type"] option:selected[value="block"]:first')
    var $allBlockOptions = $('[data-role="select-block-type"] option[value="block"]')

    if ($selectedBlockOption.length === 0) {
      $allBlockOptions.removeAttr('disabled')

      return
    }

    $allBlockOptions.not($selectedBlockOption).attr('disabled', 'disabled')
  },

  toggleVisibilityBlock: function (e) {
    // get checkbox
    var $checkbox = $(e.currentTarget)
    var $blackWrapper = $checkbox.parents('[data-role="page-block-wrapper"]')
    var $label = $blackWrapper.find('label[for="' + $checkbox.attr('id') + '"]')

    if ($blackWrapper.hasClass('block-not-visible')) {
      // make visible
      $blackWrapper.removeClass('block-not-visible')
      $label.find('[data-fa-i2svg]').attr('data-icon', 'eye')
      $label.find('.sr-only').html(jsBackend.locale.lbl('Hide'))
    } else {
      // make invisible
      $blackWrapper.addClass('block-not-visible')
      $label.find('[data-fa-i2svg]').attr('data-icon', 'eye-slash')
      $label.find('.sr-only').html(jsBackend.locale.lbl('Show'))
    }
  }
}

/**
 * Template switcher
 */
jsBackend.pages.template = {
  init: function () {
    $('[data-role="template-switcher"]').on('change', function() {
      window.location.replace(jsBackend.pages.template.getChangeTemplateUrl('template-id', $(this).val()))
    })
  },

  getChangeTemplateUrl: function (key, value, url) {
    if (!url) url = jsBackend.pages.template.getChangeTemplateUrl('report', null, window.location.href);
    var re = new RegExp("([?&])" + key + "=.*?(&|#|$)(.*)", "gi"),
      hash

    if (re.test(url)) {
      if (typeof value !== 'undefined' && value !== null) {
        return url.replace(re, '$1' + key + "=" + value + '$2$3')
      }

      hash = url.split('#')
      url = hash[0].replace(re, '$1$3').replace(/(&|\?)$/, '')
      if (typeof hash[1] !== 'undefined' && hash[1] !== null) {
        url += '#' + hash[1]
      }
      return url
    }
    if (typeof value !== 'undefined' && value !== null) {
      var separator = url.indexOf('?') !== -1 ? '&' : '?'
      hash = url.split('#')
      url = hash[0] + separator + key + '=' + value
      if (typeof hash[1] !== 'undefined' && hash[1] !== null) {
        url += '#' + hash[1]
      }
      return url
    }

    return url
  }
}

/**
 * All methods related to the pages tree
 */
jsBackend.pages.tree = {
  // init, something like a constructor
  init: function () {
    if ($('#tree').find('> [data-tree]').length === 0) return false

    var openedIds = []
    if (typeof pageID !== 'undefined') {
      // get parents
      var parents = $('#page-' + pageID).parents('li')

      // init var
      openedIds = ['page-' + pageID]

      // add parents
      for (var i = 0; i < parents.length; i++) {
        openedIds.push($(parents[i]).prop('id'))
      }
    }

    // add home if needed
    if (!utils.array.inArray('page-1', openedIds)) openedIds.push('page-1')

    // jsTree options
    var options = {
      core: {
        animation: 0,
        themes: {
          name: 'proton',
          responsive: true
        },
        multiple: false,
        check_callback: jsBackend.pages.tree.checkCallback
      },
      types: {
        default: {
          icon: 'far fa-file'
        },
        home: {
          icon: 'fas fa-home'
        },
        anchor: {
          icon: 'far fa-file'
        },
        sitemap: {
          icon: 'fas fa-sitemap'
        },
        error: {
          icon: 'fas fa-exclamation-triangle'
        }
      },
      search: {
        show_only_matches: true
      },
      dnd: {
        inside_pos: 'last'
      },
      plugins: ['dnd', 'types', 'state', 'search']
    }

    // Init page tree
    var jsTreeInstance = $('#tree').find('> [data-tree]').jstree(options)

    jsTreeInstance.on('move_node.jstree', function (event, data) {
      jsBackend.pages.tree.onMove(event, data, jsTreeInstance)
    })
    jsTreeInstance.on('copy_node.jstree', function (event, data) {
      jsBackend.pages.tree.onMove(event, data, jsTreeInstance)
    })

    // Search through pages
    var searchThroughPages = function () {
      var v = $('.js-tree-search').val()
      $('#tree').find('> [data-tree]').each(function () {
        $(this).jstree(true).search(v)
      })
    }
    $('.js-tree-search').bind('keyup input', utils.events.debounce(searchThroughPages, 150))

    // To prevent FOUC, we only show the jsTree when it's done loading.
    jsTreeInstance.on('ready.jstree', function (e, data) {
      $('#tree').show()
    })

    // On selecting a node in the tree, visit the anchor.
    jsTreeInstance.on('select_node.jstree', function (e, data, jsTreeInstance) {
      if (data && data.node && data.event) {
        // Get current and new URL
        var node = data.node
        var currentPageURL = window.location.pathname + window.location.search
        var newPageURL = node.a_attr.href

        // Only redirect if destination isn't the current one.
        if (typeof newPageURL !== 'undefined' && newPageURL !== currentPageURL) {
          window.location = newPageURL
        }
      }
    })

    // set the item selected
    if (typeof selectedId !== 'undefined') $('#' + selectedId).addClass('selected')

    jsBackend.pages.tree.toggleJsTreeCollapse(jsTreeInstance)
  },

  checkCallback: function (operation, node, parent) {
    if (operation !== 'move_node') {
      return true
    }

    if (!node.data.allowMove) {
      return false
    }

    if (typeof parent.data === 'undefined' || parent.data === null) {
      return typeof parent.children === 'undefined' || parent.children[0] !== 'page-1'
    }

    return parent.data.allowChildren
  },

  // when an item is moved
  onMove: function (event, data, jsTreeInstance) {
    var tree = data.new_instance.element.first().data('tree')

    var node = event.type === 'copy_node' ? data.original : data.node
    // get pageID that has to be moved
    var currentPageID = node.id.replace('page-', '')

    // set the default type of moving
    var type = 'before'

    // get the position where the page is dropped
    var droppedOnElement = null
    if (data.new_instance._model.data[data.parent].children[data.position + 1] !== undefined) {
      droppedOnElement = $('#' + data.new_instance._model.data[data.parent].children[data.position + 1])
    }

    // when node is dropped on the last position, we can not add it before the last one -> we need after
    if (data.new_instance._model.data[data.parent].children[data.position + 1] === undefined
      && data.new_instance._model.data[data.parent].children[data.position - 1] !== undefined) {
      droppedOnElement = $('#' + data.new_instance._model.data[data.parent].children[data.position - 1])
      type = 'after'
    }

    // when node is dropped on another node to move it inside
    if (data.new_instance._model.data[data.parent].children.length === 1 && data.new_instance._model.data[data.parent].children[0] === node.id) {
      type = 'inside'
      if (data.parent !== undefined && data.parent !== '#') {
        droppedOnElement = $('#' + data.parent)
      }
    }

    // get pageID wheron the page has been dropped
    var droppedOnPageID = 0
    if (droppedOnElement !== null) {
      droppedOnPageID = droppedOnElement.prop('id').replace('page-', '')
    }

    // move the page
    $.ajax({
      data: {
        fork: {action: 'Move'},
        id: currentPageID,
        dropped_on: droppedOnPageID,
        type: type,
        tree: tree
      },
      success: function (json, textStatus) {
        if (json.code !== 200) {
          if (jsBackend.debug) window.alert(textStatus)

          // show message
          jsBackend.messages.add('danger', jsBackend.locale.err('CantBeMoved'))

          // rollback
          jsTreeInstance.jstree('refresh')

          return
        }

        // show message
        jsBackend.messages.add('success', jsBackend.locale.msg('PageIsMoved').replace('%1$s', json.data.title))
      },
      error: function (XMLHttpRequest, textStatus, errorThrown) {
        window.location.reload()
      },
    })
  },

  toggleJsTreeCollapse: function (jsTreeInstance) {
    $('[data-role="toggle-js-tree-collapse"]').on('click', function () {
      var $this = $(this)
      $this.toggleClass('tree-collapsed')
      var collapsed = $this.hasClass('tree-collapsed')
      var $buttonText = $('[data-role="toggle-js-tree-collapse-text"]')

      if (collapsed) {
        $buttonText.html(jsBackend.locale.lbl('OpenTreeNavigation'))
        jsTreeInstance.jstree('close_all')

        return
      }

      $buttonText.html(jsBackend.locale.lbl('CloseTreeNavigation'))
      jsTreeInstance.jstree('open_all')
    })
  }
}

$(jsBackend.pages.init)
<|endoftext|>


<commit_before>import {
  hasAnySpecialEducationData,
  prettyProgramOrPlacementText,
  prettyIepTextForSpecialEducationStudent
} from './specialEducation';

describe('#hasAnySpecialEducationData', () => {
  it('excludes other programs', () => {
    expect(hasAnySpecialEducationData({
      program_assigned: '2Way English',
      sped_placement: null,
      disability: null,
      sped_level_of_need: null,
    }, null)).toEqual(false);
  });
});

describe('#prettyProgramOrPlacementText', () => {
  it('works', () => {
    expect(prettyProgramOrPlacementText({
      program_assigned: null,
      sped_placement: null
    })).toEqual(null);

    expect(prettyProgramOrPlacementText({
      program_assigned: 'Reg Ed',
      sped_placement: null
    })).toEqual(null);

    expect(prettyProgramOrPlacementText({
      program_assigned: 'Sp Ed',
      sped_placement: null
    })).toEqual('Sp Ed');

    expect(prettyProgramOrPlacementText({
      program_assigned: 'Sp Ed',
      sped_placement: 'Full Inclusion'
    })).toEqual('Full Inclusion');

    expect(prettyProgramOrPlacementText({
      program_assigned: 'SEIP',
      sped_placement: null
    })).toEqual(null);

    expect(prettyProgramOrPlacementText({
      program_assigned: 'Waivered SEIP',
      sped_placement: null
    })).toEqual(null);

    expect(prettyProgramOrPlacementText({
      program_assigned: '2Way Spanish',
      sped_placement: 'foo'
    })).toEqual('2Way Spanish');
  });
});

/*
These test cases are sampled across districts from:
  puts Student.active.select(:program_assigned, :sped_placement, :disability, :sped_level_of_need).as_json.uniq.to_json;nil
*/
describe('#prettyIepTextForSpecialEducationStudent', () => {
  it('works for typical test cases', () => {

    expect(prettyIepTextForSpecialEducationStudent({
      program_assigned: null,
      sped_placement: '',
      disability: 'Intellectual',
      sped_level_of_need: 'High'
    })).toEqual('IEP for Intellectual');

    expect(prettyIepTextForSpecialEducationStudent({
      program_assigned: null,
      sped_placement: '',
      disability: 'Communication',
      sped_level_of_need: 'Low < 2'
    })).toEqual('IEP for Communication');

    expect(prettyIepTextForSpecialEducationStudent({
      program_assigned: null,
      sped_placement: 'Full Inclusion',
      disability: 'Communication',
      sped_level_of_need: 'Low < 2'
    })).toEqual('IEP for Communication');

    expect(prettyIepTextForSpecialEducationStudent({
      program_assigned: null,
      sped_placement: 'Full Inclusion',
      disability: 'Emotional',
      sped_level_of_need: 'High'
    })).toEqual('IEP for Emotional');

    expect(prettyIepTextForSpecialEducationStudent({
      program_assigned: null,
      sped_placement: 'Partial Inclusion',
      disability: 'Specific LDs',
      sped_level_of_need: 'Moderate'
    })).toEqual('IEP for Specific LDs');

    expect(prettyIepTextForSpecialEducationStudent({
      program_assigned: null,
      sped_placement: 'Public Seperate', // typo in SIS upstream
      disability: 'Specific LDs',
      sped_level_of_need: 'Moderate'
    })).toEqual('IEP for Specific LDs');

    expect(prettyIepTextForSpecialEducationStudent({
      program_assigned: null,
      sped_placement: 'Substantially Separate',
      disability: 'Autism',
      sped_level_of_need: 'Moderate'
    })).toEqual('IEP for Autism');

    expect(prettyIepTextForSpecialEducationStudent({
      program_assigned: null,
      sped_placement: 'Public Day',
      disability: 'Health',
      sped_level_of_need: 'High'
    })).toEqual('IEP for Health');

    expect(prettyIepTextForSpecialEducationStudent({
      program_assigned: null,
      sped_placement: 'Not special ed',
      disability: 'Sensory (Hearing)',
      sped_level_of_need: 'Low-2+ hrs/week'
    })).toEqual('IEP for Sensory (Hearing)');

    expect(prettyIepTextForSpecialEducationStudent({
      program_assigned: null,
      sped_placement: 'Full Inclusion',
      disability: 'Developmental Delay',
      sped_level_of_need: 'Low-Less Than 2hrs/week'
    })).toEqual('IEP for Developmental Delay');

    expect(prettyIepTextForSpecialEducationStudent({
      program_assigned: null,
      sped_placement: 'Substantially Separate',
      disability: 'Multiple Disabilities',
      sped_level_of_need: 'Moderate'
    })).toEqual('IEP for Multiple Disabilities');
  });

  it('shows generic IEP for "Not special ed" and "Does Not Apply"', () => {
    expect(prettyIepTextForSpecialEducationStudent({
      program_assigned: null,
      sped_placement: 'Not special ed',
      disability: 'Does Not Apply',
      sped_level_of_need: 'Does Not Apply'
    })).toEqual('IEP');
  });

  it('shows info about "Exited this year"', () => {
    expect(prettyIepTextForSpecialEducationStudent({
      program_assigned: null,
      sped_placement: 'Exited this year',
      disability: 'Emotional',
      sped_level_of_need: 'High'
    })).toEqual('Exited SPED this year');
  });

  it('always show fallback IEP text even if mistakenly called for non-special ed program assignment', () => {
    expect(prettyIepTextForSpecialEducationStudent({
      program_assigned: '2Way English',
      sped_placement: null,
      disability: null,
      sped_level_of_need: null,
    })).toEqual('IEP');
  });
});
<commit_msg>Update tests<commit_after>import {
  hasAnySpecialEducationData,
  prettyProgramOrPlacementText,
  prettyIepTextForSpecialEducationStudent
} from './specialEducation';

describe('#hasAnySpecialEducationData', () => {
  it('excludes other programs', () => {
    expect(hasAnySpecialEducationData({
      program_assigned: '2Way English',
      sped_placement: null,
      disability: null,
      sped_level_of_need: null,
    }, null)).toEqual(false);
  });

  it('ignores sped_liaison value', () => {
    expect(hasAnySpecialEducationData({
      program_assigned: 'Reg Ed',
      sped_placement: null,
      disability: null,
      sped_level_of_need: null,
      sped_liaison: 'Concepcion'
    }, null)).toEqual(false);
  });
});

describe('#prettyProgramOrPlacementText', () => {
  it('works', () => {
    expect(prettyProgramOrPlacementText({
      program_assigned: null,
      sped_placement: null
    })).toEqual(null);

    expect(prettyProgramOrPlacementText({
      program_assigned: 'Reg Ed',
      sped_placement: null
    })).toEqual(null);

    expect(prettyProgramOrPlacementText({
      program_assigned: 'Sp Ed',
      sped_placement: null
    })).toEqual('Sp Ed');

    expect(prettyProgramOrPlacementText({
      program_assigned: 'Sp Ed',
      sped_placement: 'Full Inclusion'
    })).toEqual('Full Inclusion');

    expect(prettyProgramOrPlacementText({
      program_assigned: 'SEIP',
      sped_placement: null
    })).toEqual(null);

    expect(prettyProgramOrPlacementText({
      program_assigned: 'Waivered SEIP',
      sped_placement: null
    })).toEqual(null);

    expect(prettyProgramOrPlacementText({
      program_assigned: 'Sheltered Immersion',
      sped_placement: null
    })).toEqual(null);

    expect(prettyProgramOrPlacementText({
      program_assigned: '2Way Spanish',
      sped_placement: 'foo'
    })).toEqual('2Way Spanish');
  });
});

/*
These test cases are sampled across districts from:
  puts Student.active.select(:program_assigned, :sped_placement, :disability, :sped_level_of_need).as_json.uniq.to_json;nil
*/
describe('#prettyIepTextForSpecialEducationStudent', () => {
  it('works for typical test cases', () => {

    expect(prettyIepTextForSpecialEducationStudent({
      program_assigned: null,
      sped_placement: '',
      disability: 'Intellectual',
      sped_level_of_need: 'High'
    })).toEqual('IEP for Intellectual');

    expect(prettyIepTextForSpecialEducationStudent({
      program_assigned: null,
      sped_placement: '',
      disability: 'Communication',
      sped_level_of_need: 'Low < 2'
    })).toEqual('IEP for Communication');

    expect(prettyIepTextForSpecialEducationStudent({
      program_assigned: null,
      sped_placement: 'Full Inclusion',
      disability: 'Communication',
      sped_level_of_need: 'Low < 2'
    })).toEqual('IEP for Communication');

    expect(prettyIepTextForSpecialEducationStudent({
      program_assigned: null,
      sped_placement: 'Full Inclusion',
      disability: 'Emotional',
      sped_level_of_need: 'High'
    })).toEqual('IEP for Emotional');

    expect(prettyIepTextForSpecialEducationStudent({
      program_assigned: null,
      sped_placement: 'Partial Inclusion',
      disability: 'Specific LDs',
      sped_level_of_need: 'Moderate'
    })).toEqual('IEP for Specific LDs');

    expect(prettyIepTextForSpecialEducationStudent({
      program_assigned: null,
      sped_placement: 'Public Seperate', // typo in SIS upstream
      disability: 'Specific LDs',
      sped_level_of_need: 'Moderate'
    })).toEqual('IEP for Specific LDs');

    expect(prettyIepTextForSpecialEducationStudent({
      program_assigned: null,
      sped_placement: 'Substantially Separate',
      disability: 'Autism',
      sped_level_of_need: 'Moderate'
    })).toEqual('IEP for Autism');

    expect(prettyIepTextForSpecialEducationStudent({
      program_assigned: null,
      sped_placement: 'Public Day',
      disability: 'Health',
      sped_level_of_need: 'High'
    })).toEqual('IEP for Health');

    expect(prettyIepTextForSpecialEducationStudent({
      program_assigned: null,
      sped_placement: 'Not special ed',
      disability: 'Sensory (Hearing)',
      sped_level_of_need: 'Low-2+ hrs/week'
    })).toEqual('IEP for Sensory (Hearing)');

    expect(prettyIepTextForSpecialEducationStudent({
      program_assigned: null,
      sped_placement: 'Full Inclusion',
      disability: 'Developmental Delay',
      sped_level_of_need: 'Low-Less Than 2hrs/week'
    })).toEqual('IEP for Developmental Delay');

    expect(prettyIepTextForSpecialEducationStudent({
      program_assigned: null,
      sped_placement: 'Substantially Separate',
      disability: 'Multiple Disabilities',
      sped_level_of_need: 'Moderate'
    })).toEqual('IEP for Multiple Disabilities');
  });

  it('shows generic IEP for "Not special ed" and "Does Not Apply"', () => {
    expect(prettyIepTextForSpecialEducationStudent({
      program_assigned: null,
      sped_placement: 'Not special ed',
      disability: 'Does Not Apply',
      sped_level_of_need: 'Does Not Apply'
    })).toEqual('IEP');
  });

  it('shows info about "Exited this year"', () => {
    expect(prettyIepTextForSpecialEducationStudent({
      program_assigned: null,
      sped_placement: 'Exited this year',
      disability: 'Emotional',
      sped_level_of_need: 'High'
    })).toEqual('Exited SPED this year');
  });

  it('always show fallback IEP text even if mistakenly called for non-special ed program assignment', () => {
    expect(prettyIepTextForSpecialEducationStudent({
      program_assigned: '2Way English',
      sped_placement: null,
      disability: null,
      sped_level_of_need: null,
    })).toEqual('IEP');
  });
});
<|endoftext|>


<commit_before>'use strict';

/**
 * Expose main package JSON of the application
 * with basic info, dependencies, etc.
 */

module.exports = opts => {
  const {
    strapiDependencies,
    additionalsDependencies,
    strapiVersion,
    projectName,
    uuid,
    packageJsonStrapi,
  } = opts;

  // Finally, return the JSON.
  return {
    name: projectName,
    private: true,
    version: '0.1.0',
    description: 'A Strapi application',
    scripts: {
      develop: 'strapi develop',
      start: 'strapi start',
      build: 'strapi build',
      strapi: 'strapi',
    },
    devDependencies: {},
    dependencies: Object.assign(
      {},
      strapiDependencies.reduce((acc, key) => {
        acc[key] = strapiVersion;
        return acc;
      }, {}),
      additionalsDependencies
    ),
    author: {
      name: 'A Strapi developer',
    },
    strapi: {
      uuid,
      ...packageJsonStrapi,
    },
    engines: {
      node: '>=12.x.x <=14.x.x',
      npm: '^6.0.0',
    },
    license: 'MIT',
  };
};
<commit_msg>Enable Node 16 and NPM 7/8 support on generation<commit_after>'use strict';

/**
 * Expose main package JSON of the application
 * with basic info, dependencies, etc.
 */

module.exports = opts => {
  const {
    strapiDependencies,
    additionalsDependencies,
    strapiVersion,
    projectName,
    uuid,
    packageJsonStrapi,
  } = opts;

  // Finally, return the JSON.
  return {
    name: projectName,
    private: true,
    version: '0.1.0',
    description: 'A Strapi application',
    scripts: {
      develop: 'strapi develop',
      start: 'strapi start',
      build: 'strapi build',
      strapi: 'strapi',
    },
    devDependencies: {},
    dependencies: Object.assign(
      {},
      strapiDependencies.reduce((acc, key) => {
        acc[key] = strapiVersion;
        return acc;
      }, {}),
      additionalsDependencies
    ),
    author: {
      name: 'A Strapi developer',
    },
    strapi: {
      uuid,
      ...packageJsonStrapi,
    },
    engines: {
      node: '>=12.x.x <=16.x.x',
      npm: '>=6.0.0',
    },
    license: 'MIT',
  };
};
<|endoftext|>


<commit_before>import React, { useEffect, useState } from 'react';
import { PropTypes as T } from 'prop-types';
import { connect } from 'react-redux';
import qs from 'qs';

import {
  fetchLocations as fetchLocationsAction,
  invalidateLocations as invalidateLocationsAction,
  openDownloadModal as openDownloadModalAction,
  fetchBaseData as fetchBaseDataAction,
} from '../../actions/action-creators';
import { buildQS } from '../../utils/url';

import { HubHeader } from '../../components/header';
import Filter from '../../components/filter';
import Results from './results';

const PER_PAGE = 15;

function getPage(query) {
  if (query && query.page) {
    let page = query.page;
    page = isNaN(page) || page < 1 ? 1 : +page;
    return page;
  }
  return 1;
}

export default function LocationsHub({
  fetchLocations,
  invalidateLocations,
  openDownloadModal,
  fetchBaseData,

  fetching,
  fetched,
  error,
  results,
  meta,
  countries,

  location,
  history,
}) {
  const [filters, setFilters] = useState({});
  const [page, setPage] = useState(1);

  const [isMounted, setIsMounted] = useState(false);
  useEffect(() => {
    setIsMounted(true);
  }, []);

  useEffect(() => {
    // on url change
    let query = qs.parse(location.search, {
      ignoreQueryPrefix: true,
    });
    setPage(() => getPage(query));

    setFilters({
      // In the front end we are using param 'area', but this is
      // mapped to 'city' before getting sent to backend.
      order_by: query.order_by === 'area' ? 'city' : query.order_by,
      sort: 'desc',
      parameter: query.parameters && query.parameters.split(','),
      country: query.countries && query.countries.split(','),
      sourceName: query.sources && query.sources.split(','),
      // The following are not lists
      isMobile: query.mobility && query.mobility === 'Mobile',
      entity: query.entity && query.entity.toLowerCase(),
      sensorType: query.grade && query.grade.toLowerCase(),
      manufacturerName: query.manufacturer && query.manufacturer,
    });
  }, [location]);

  useEffect(() => {
    if (!isMounted) return;
    fetchLocations(page, filters, PER_PAGE);
    return () => invalidateLocations();
  }, [page, filters, isMounted]);

  function handlePageClick(d) {
    let query = qs.parse(location.search, { ignoreQueryPrefix: true });
    query.page = d.selected + 1;

    history.push(`/locations?${buildQS(query)}`);
  }

  useEffect(() => {
    fetchBaseData();
  }, []);

  const countryCount = countries ? countries.length : 'many';

  const totalPages = Math.ceil(meta.found / PER_PAGE);

  return (

      <HubHeader title="Location" countriesCount={countryCount} />

        <Filter
          slug="/locations"
          by={['parameters', 'countries', 'sources', 'sensor']}
          orderByOptions={['location', 'country', 'area', 'count']}
        />

Results

                {meta.found ? (

                    A total of {meta.found} locations were
                    found

                ) : null}

          <Results
            fetched={fetched}
            fetching={fetching}
            error={error}
            locations={results}
            totalPages={totalPages}
            page={page}
            openDownloadModal={openDownloadModal}
            handlePageClick={handlePageClick}
          />

  );
}

LocationsHub.propTypes = {
  fetching: T.bool,
  fetched: T.bool,
  error: T.object,
  results: T.array,
  meta: T.object,
  countries: T.array,

  location: T.object,
  history: T.object,

  fetchBaseData: T.func,
  fetchLocations: T.func,
  invalidateLocations: T.func,
  openDownloadModal: T.func,
};

// /////////////////////////////////////////////////////////////////////
// Connect functions

function selector(state) {
  return {
    fetching: state.locations.fetching,
    fetched: state.locations.fetched,
    error: state.locations.error,
    results: state.locations.data.results,
    meta: state.locations.data.meta,
    countries: state.baseData.countries,
  };
}

function dispatcher(dispatch) {
  return {
    fetchLocations: (...args) => dispatch(fetchLocationsAction(...args)),
    invalidateLocations: (...args) =>
      dispatch(invalidateLocationsAction(...args)),
    openDownloadModal: (...args) => dispatch(openDownloadModalAction(...args)),
    fetchBaseData: (...args) => dispatch(fetchBaseDataAction(...args)),
  };
}

module.exports = connect(selector, dispatcher)(LocationsHub);
<commit_msg>Fix city region<commit_after>import React, { useEffect, useState } from 'react';
import { PropTypes as T } from 'prop-types';
import { connect } from 'react-redux';
import qs from 'qs';

import {
  fetchLocations as fetchLocationsAction,
  invalidateLocations as invalidateLocationsAction,
  openDownloadModal as openDownloadModalAction,
  fetchBaseData as fetchBaseDataAction,
} from '../../actions/action-creators';
import { buildQS } from '../../utils/url';

import { HubHeader } from '../../components/header';
import Filter from '../../components/filter';
import Results from './results';

const PER_PAGE = 15;

function getPage(query) {
  if (query && query.page) {
    let page = query.page;
    page = isNaN(page) || page < 1 ? 1 : +page;
    return page;
  }
  return 1;
}

export default function LocationsHub({
  fetchLocations,
  invalidateLocations,
  openDownloadModal,
  fetchBaseData,

  fetching,
  fetched,
  error,
  results,
  meta,
  countries,

  location,
  history,
}) {
  const [filters, setFilters] = useState({});
  const [page, setPage] = useState(1);

  const [isMounted, setIsMounted] = useState(false);
  useEffect(() => {
    setIsMounted(true);
  }, []);

  useEffect(() => {
    // on url change
    let query = qs.parse(location.search, {
      ignoreQueryPrefix: true,
    });
    setPage(() => getPage(query));

    setFilters({
      // In the front end we are using param 'area', but this is
      // mapped to 'city' before getting sent to backend.
      order_by: query.order_by === 'City/Region' ? 'city' : query.order_by,
      sort: 'desc',
      parameter: query.parameters && query.parameters.split(','),
      country: query.countries && query.countries.split(','),
      sourceName: query.sources && query.sources.split(','),
      // The following are not lists
      isMobile: query.mobility && query.mobility === 'Mobile',
      entity: query.entity && query.entity.toLowerCase(),
      sensorType: query.grade && query.grade.toLowerCase(),
      manufacturerName: query.manufacturer && query.manufacturer,
    });
  }, [location]);

  useEffect(() => {
    if (!isMounted) return;
    fetchLocations(page, filters, PER_PAGE);
    return () => invalidateLocations();
  }, [page, filters, isMounted]);

  function handlePageClick(d) {
    let query = qs.parse(location.search, { ignoreQueryPrefix: true });
    query.page = d.selected + 1;

    history.push(`/locations?${buildQS(query)}`);
  }

  useEffect(() => {
    fetchBaseData();
  }, []);

  const countryCount = countries ? countries.length : 'many';

  const totalPages = Math.ceil(meta.found / PER_PAGE);

  return (

      <HubHeader title="Location" countriesCount={countryCount} />

        <Filter
          slug="/locations"
          by={['parameters', 'countries', 'sources', 'sensor']}
          orderByOptions={['location', 'country', 'City/Region', 'count']}
        />

Results

                {meta.found ? (

                    A total of {meta.found} locations were
                    found

                ) : null}

          <Results
            fetched={fetched}
            fetching={fetching}
            error={error}
            locations={results}
            totalPages={totalPages}
            page={page}
            openDownloadModal={openDownloadModal}
            handlePageClick={handlePageClick}
          />

  );
}

LocationsHub.propTypes = {
  fetching: T.bool,
  fetched: T.bool,
  error: T.object,
  results: T.array,
  meta: T.object,
  countries: T.array,

  location: T.object,
  history: T.object,

  fetchBaseData: T.func,
  fetchLocations: T.func,
  invalidateLocations: T.func,
  openDownloadModal: T.func,
};

// /////////////////////////////////////////////////////////////////////
// Connect functions

function selector(state) {
  return {
    fetching: state.locations.fetching,
    fetched: state.locations.fetched,
    error: state.locations.error,
    results: state.locations.data.results,
    meta: state.locations.data.meta,
    countries: state.baseData.countries,
  };
}

function dispatcher(dispatch) {
  return {
    fetchLocations: (...args) => dispatch(fetchLocationsAction(...args)),
    invalidateLocations: (...args) =>
      dispatch(invalidateLocationsAction(...args)),
    openDownloadModal: (...args) => dispatch(openDownloadModalAction(...args)),
    fetchBaseData: (...args) => dispatch(fetchBaseDataAction(...args)),
  };
}

module.exports = connect(selector, dispatcher)(LocationsHub);
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import config from 'config/';
import Widget from '../Widget';
import AccountsWidget from './AccountsWidget';
import RefreshButton from 'components/RefreshButton';
import InactiveAccountsWidget from './InactiveAccountsWidget';
import { Link } from 'react-router';
import PanelLoading from 'components/loading/Panel';
import TableLoading from 'components/loading/VerticalTable';

class Accounts extends Component {

  static defaultProps = {
    widget: {},
    widgetQuery: {
      url: 'accounts',
      process: (data) => {
        return {
          accounts: data.map((user) => {
            if( !user.lastLogin ) {
              return user;
            }
            let loginInt = parseInt(user.lastLogin);
            // string
            if(isNaN(loginInt)) {
              user.lastLogin = false;
              return user;
            }
            // php timestamp convert
            let lastLogin = window.moment(loginInt*1000);
            if(!lastLogin || !lastLogin._isAMomentObject) {
              user.lastLogin = false;
              return user;
            }
            user.lastLogin = lastLogin.format('MMMM Do YYYY, h:mm:ss a');
            return user;
          })
        };
      }
    }
  }

  componentWillMount () {
    Widget.registerWidget(
      this,
      true
    );
  }

  // Returns accounts filtered by if they have lastLogin or not
  getInactiveAccounts() {
    return this.props.widget.data.accounts.filter((user) => {
      if( !user.lastLogin ) {
        return false;
      }
      const lastLogin = window.moment(user.lastLogin, 'MMMM Do YYYY, h:mm:ss a');
      const days = window.moment().diff(lastLogin, 'days');
      return days && days % 1 === 0 && days > 30 && days < 10000;
    });
  }

  render () {
    let { widget, widgetType }  = this.props;

    if(window.loadShow) {
      if (widgetType === 'inactive') {
        return <TableLoading text={true} colCount={1} />;
      } else  {
        return <PanelLoading />;
      }
    }

    // Return loading if not set
    if(!widget.status || widget.status !== 'loaded') {
      if (widgetType === 'inactive') {
        return <TableLoading text={true} colCount={1} />;
      } else  {
        return <PanelLoading />;
      }
    }

    let userUrl, adminRole;
    // CMS Specific
    switch(config.cms) {
      case 'wordpress':
        adminRole = 'administrator';
        userUrl = config.siteUrl + '/wp-admin/users.php';
        break;
      case 'drupal':
        adminRole = 'administrator';
        userUrl = config.siteUrl + '/admin/people';
        break;
    }

    // Inactive
    if(widgetType === 'inactive') {
      return (
        <InactiveAccountsWidget
          userUrl={userUrl}
          refreshButton={(<RefreshButton status={widget.data.lastStatus} widgetName={this.props.widgetName} widgetQuery={this.props.widgetQuery} />)}
          accounts={this.getInactiveAccounts()} />
      )
    }
    // Normal accounts widget
    else {

      let admins = 0;
      let totalAccounts = 0;
      // Compile data
      if (widget.data && widget.data.accounts && widget.data.accounts.length) {
        widget.data.accounts.map((account) => {
          if (account.superAdmin) {
            admins++;
          }
        });
        totalAccounts = widget.data.accounts.length;
      }

      // @TODO?
      if(this.props.display === 'page') {
        return (

AccountsPage

        )
      }
      else {
        return (
          <AccountsWidget
            admins={admins}
            totalAccounts={totalAccounts}
            refreshButton={(<RefreshButton status={widget.data.lastStatus} widgetName={this.props.widgetName} widgetQuery={this.props.widgetQuery} />)}
            footer={Widget.panelFooter(totalAccounts + ' total accounts', userUrl, true)} />
        )
      }
    }
  }
}

Accounts.propTypes = Widget.propTypes();

export default Widget.connect(Accounts);
<commit_msg>updating for new Accounts return value<commit_after>import React, { Component } from 'react';
import config from 'config/';
import Widget from '../Widget';
import AccountsWidget from './AccountsWidget';
import RefreshButton from 'components/RefreshButton';
import InactiveAccountsWidget from './InactiveAccountsWidget';
import { Link } from 'react-router';
import PanelLoading from 'components/loading/Panel';
import TableLoading from 'components/loading/VerticalTable';

class Accounts extends Component {

  static defaultProps = {
    widget: {},
    widgetQuery: {
      url: 'accounts',
      process: (data) => {
        return {
          lastStatus: data.lastStatus,
          accounts: data.accounts.map((user) => {
            if (!user.lastLogin) {
              return user;
            }
            let loginInt = parseInt(user.lastLogin);
            // string
            if (isNaN(loginInt)) {
              user.lastLogin = false;
              return user;
            }
            // php timestamp convert
            let lastLogin = window.moment(loginInt * 1000);
            if (!lastLogin || !lastLogin._isAMomentObject) {
              user.lastLogin = false;
              return user;
            }
            user.lastLogin = lastLogin.format('MMMM Do YYYY, h:mm:ss a');
            return user;
          })
        };
      }
    }
  }

  componentWillMount () {
    Widget.registerWidget(
      this,
      true
    );
  }

  // Returns accounts filtered by if they have lastLogin or not
  getInactiveAccounts() {
    return this.props.widget.data.accounts.filter((user) => {
      if( !user.lastLogin ) {
        return false;
      }
      const lastLogin = window.moment(user.lastLogin, 'MMMM Do YYYY, h:mm:ss a');
      const days = window.moment().diff(lastLogin, 'days');
      return days && days % 1 === 0 && days > 30 && days < 10000;
    });
  }

  render () {
    let { widget, widgetType }  = this.props;

    if(window.loadShow) {
      if (widgetType === 'inactive') {
        return <TableLoading text={true} colCount={1} />;
      } else  {
        return <PanelLoading />;
      }
    }

    // Return loading if not set
    if(!widget.status || widget.status !== 'loaded') {
      if (widgetType === 'inactive') {
        return <TableLoading text={true} colCount={1} />;
      } else  {
        return <PanelLoading />;
      }
    }

    let userUrl, adminRole;
    // CMS Specific
    switch(config.cms) {
      case 'wordpress':
        adminRole = 'administrator';
        userUrl = config.siteUrl + '/wp-admin/users.php';
        break;
      case 'drupal':
        adminRole = 'administrator';
        userUrl = config.siteUrl + '/admin/people';
        break;
    }

    // Inactive
    if(widgetType === 'inactive') {
      return (
        <InactiveAccountsWidget
          userUrl={userUrl}
          refreshButton={(<RefreshButton status={widget.data.lastStatus} widgetName={this.props.widgetName} widgetQuery={this.props.widgetQuery} />)}
          accounts={this.getInactiveAccounts()} />
      )
    }
    // Normal accounts widget
    else {

      let admins = 0;
      let totalAccounts = 0;
      // Compile data
      if (widget.data && widget.data.accounts && widget.data.accounts.length) {
        widget.data.accounts.map((account) => {
          if (account.superAdmin) {
            admins++;
          }
        });
        totalAccounts = widget.data.accounts.length;
      }

      // @TODO?
      if(this.props.display === 'page') {
        return (

AccountsPage

        )
      }
      else {
        return (
          <AccountsWidget
            admins={admins}
            totalAccounts={totalAccounts}
            refreshButton={(<RefreshButton status={widget.data.lastStatus} widgetName={this.props.widgetName} widgetQuery={this.props.widgetQuery} />)}
            footer={Widget.panelFooter(totalAccounts + ' total accounts', userUrl, true)} />
        )
      }
    }
  }
}

Accounts.propTypes = Widget.propTypes();

export default Widget.connect(Accounts);
<|endoftext|>


<commit_before>import React, {Component, Fragment} from 'react';
import PropTypes from 'prop-types';
import { NavLink } from 'react-router-dom';
import { getUrlSearchParams, removeQueryParam} from 'src/utils/browser-utils.js';
import AutoSuggestedTags from '../auto-suggested-tags';
import {CSVLink} from 'react-csv';
import i18n from '../../../i18n';
import { setQueryParams } from '../../../utils/browser-utils';
import LabelsList from '../../molecules/labelsList/labelsList';
import './task-filters.css';

class TaskFilters extends Component {
  constructor(props) {
    super(props);

    this.state = {
      label: [],
      CSVLink: undefined
    };

    this.handleLabelChange = this.handleLabelChange.bind(this);
    this.getTaskTypeFromProject = this.getTaskTypeFromProject.bind(this);
  }

  // Since react router doesn't support query we read it manually from the url
  static getFilterQuery() {
    const params = getUrlSearchParams();
    return params['filter'];
  }

  static getCompleteQuery() {
    const params = getUrlSearchParams();
    return params['complete'];
  }

  static getFilterText() {
    const params = getUrlSearchParams();
    return params['text'];
  }

  onCSVLink() {
    this.setState({CSVLink: <CSVLink className={'button-as-link'} data={this.props.generateCSV()}>Download Tasks as CSV (Admin)</CSVLink>});
  }

  getTaskTypeFromProject(index) {
    if (!this.props.selectedProject ||
      !this.props.selectedProject.taskTypes ||
      this.props.selectedProject.taskTypes.length <= 0 ||
      this.props.selectedProject.taskTypes.length <= index
    ) {
      return ''; //TODO - should look better like a placeholder
    } else {
      return this.props.selectedProject.taskTypes[index];
    }
  }

  // Labels is an array of ['label1','label2']
  // Tags is an object of {'label1':true,'label2':true}
  labelsPoolToTags = () => {
    const result = {};
    for (const label of this.props.labelsPool) {
      result[label] = true;
    }
    return result;
  };

  FilterLink = ({ id }) => {
    const project = (this.props.selectedProject && this.props.selectedProject.url !== 'undefined') ?
      this.props.selectedProject.url : this.props.userDefaultProject;
    const defaultTask = `/${project}/task/1`;
    const linkTitle = this.getTaskTypeFromProject(id);
    const search = setQueryParams([`filter=taskType&text=${id + 1}`]);

    const isActive = (match, location) =>
      TaskFilters.getFilterQuery(location) === 'taskType' &&
      TaskFilters.getFilterText(location) === (id + 1);

    return (
      <NavLink className={'filter-link'} isActive={isActive} to={{ pathname: defaultTask, search }}>
        {linkTitle}
      </NavLink>
    );
  };

  onPopularLabelClick = (label) => {
    this.handleLabelChange([label]);
  };

  render() {
    let downloadCSV = null;

    if (this.props.isAdmin) {
      downloadCSV = this.state.CSVLink ? this.state.CSVLink : <button className={'button-as-link'} onClick={this.onCSVLink.bind(this)}>Prepare tasks as CSV (Admin)</button>;
    }

    const project = (this.props.selectedProject && this.props.selectedProject.url !== 'undefined') ?
      this.props.selectedProject.url : this.props.userDefaultProject;

    const defaultTask = '/' + project + '/task/1';

    return (

{i18n.t('filter.category')}

          {[0, 1, 2, 3, 4].map((id) => (<this.FilterLink key={id} id={id}/>))}

{i18n.t('filter.task-type')}

          <NavLink className={'filter-link'} isActive={(match, location) => TaskFilters.getFilterQuery(location) === 'mine'} to={{ pathname: defaultTask,
            search: setQueryParams(['filter=mine'])}}>
            {i18n.t('task.my-tasks')}
          </NavLink>

          <NavLink className={'filter-link'} isActive={(match, location) => TaskFilters.getFilterQuery(location) === 'unassigned'} to={{ pathname: defaultTask,
            search: setQueryParams(['filter=unassigned'])}}>
            {i18n.t('task.free-tasks')}
          </NavLink>

          <NavLink className={'filter-link'} isActive={(match, location) => TaskFilters.getFilterQuery(location) === undefined} to={{
            pathname: defaultTask,
            search: removeQueryParam(['filter']) }}>
            {i18n.t('task.all-tasks')}
          </NavLink>

{i18n.t('filter.popular-tags')}

        <LabelsList
          labels={this.props.popularLabels}
          onLabelClick={this.onPopularLabelClick}/>

{i18n.t('filter.by-tag')}

        <AutoSuggestedTags
          value={this.state.label}
          labels={this.labelsPoolToTags()}
          placeholder={i18n.t('task.search-by-tags')}
          onChange={this.handleLabelChange}/>

        {downloadCSV}

    );
  }

  handleLabelChange(label) {
    this.setState({label});
    this.props.onLabelChange(label);
  }
}

TaskFilters.propTypes = {
  onLabelChange: PropTypes.func.isRequired,
  selectedProject: PropTypes.object,
  popularLabels: PropTypes.array,
  labelsPool: PropTypes.object.isRequired,
  generateCSV: PropTypes.func.isRequired,
  isAdmin: PropTypes.bool.isRequired,
  userDefaultProject: PropTypes.string,
};

export default TaskFilters;
<commit_msg>Removed unused import<commit_after>import React, {Component} from 'react';
import PropTypes from 'prop-types';
import { NavLink } from 'react-router-dom';
import { getUrlSearchParams, removeQueryParam} from 'src/utils/browser-utils.js';
import AutoSuggestedTags from '../auto-suggested-tags';
import {CSVLink} from 'react-csv';
import i18n from '../../../i18n';
import { setQueryParams } from '../../../utils/browser-utils';
import LabelsList from '../../molecules/labelsList/labelsList';
import './task-filters.css';

class TaskFilters extends Component {
  constructor(props) {
    super(props);

    this.state = {
      label: [],
      CSVLink: undefined
    };

    this.handleLabelChange = this.handleLabelChange.bind(this);
    this.getTaskTypeFromProject = this.getTaskTypeFromProject.bind(this);
  }

  // Since react router doesn't support query we read it manually from the url
  static getFilterQuery() {
    const params = getUrlSearchParams();
    return params['filter'];
  }

  static getCompleteQuery() {
    const params = getUrlSearchParams();
    return params['complete'];
  }

  static getFilterText() {
    const params = getUrlSearchParams();
    return params['text'];
  }

  onCSVLink() {
    this.setState({CSVLink: <CSVLink className={'button-as-link'} data={this.props.generateCSV()}>Download Tasks as CSV (Admin)</CSVLink>});
  }

  getTaskTypeFromProject(index) {
    if (!this.props.selectedProject ||
      !this.props.selectedProject.taskTypes ||
      this.props.selectedProject.taskTypes.length <= 0 ||
      this.props.selectedProject.taskTypes.length <= index
    ) {
      return ''; //TODO - should look better like a placeholder
    } else {
      return this.props.selectedProject.taskTypes[index];
    }
  }

  // Labels is an array of ['label1','label2']
  // Tags is an object of {'label1':true,'label2':true}
  labelsPoolToTags = () => {
    const result = {};
    for (const label of this.props.labelsPool) {
      result[label] = true;
    }
    return result;
  };

  FilterLink = ({ id }) => {
    const project = (this.props.selectedProject && this.props.selectedProject.url !== 'undefined') ?
      this.props.selectedProject.url : this.props.userDefaultProject;
    const defaultTask = `/${project}/task/1`;
    const linkTitle = this.getTaskTypeFromProject(id);
    const search = setQueryParams([`filter=taskType&text=${id + 1}`]);

    const isActive = (match, location) =>
      TaskFilters.getFilterQuery(location) === 'taskType' &&
      TaskFilters.getFilterText(location) === (id + 1);

    return (
      <NavLink className={'filter-link'} isActive={isActive} to={{ pathname: defaultTask, search }}>
        {linkTitle}
      </NavLink>
    );
  };

  onPopularLabelClick = (label) => {
    this.handleLabelChange([label]);
  };

  render() {
    let downloadCSV = null;

    if (this.props.isAdmin) {
      downloadCSV = this.state.CSVLink ? this.state.CSVLink : <button className={'button-as-link'} onClick={this.onCSVLink.bind(this)}>Prepare tasks as CSV (Admin)</button>;
    }

    const project = (this.props.selectedProject && this.props.selectedProject.url !== 'undefined') ?
      this.props.selectedProject.url : this.props.userDefaultProject;

    const defaultTask = '/' + project + '/task/1';

    return (

{i18n.t('filter.category')}

          {[0, 1, 2, 3, 4].map((id) => (<this.FilterLink key={id} id={id}/>))}

{i18n.t('filter.task-type')}

          <NavLink className={'filter-link'} isActive={(match, location) => TaskFilters.getFilterQuery(location) === 'mine'} to={{ pathname: defaultTask,
            search: setQueryParams(['filter=mine'])}}>
            {i18n.t('task.my-tasks')}
          </NavLink>

          <NavLink className={'filter-link'} isActive={(match, location) => TaskFilters.getFilterQuery(location) === 'unassigned'} to={{ pathname: defaultTask,
            search: setQueryParams(['filter=unassigned'])}}>
            {i18n.t('task.free-tasks')}
          </NavLink>

          <NavLink className={'filter-link'} isActive={(match, location) => TaskFilters.getFilterQuery(location) === undefined} to={{
            pathname: defaultTask,
            search: removeQueryParam(['filter']) }}>
            {i18n.t('task.all-tasks')}
          </NavLink>

{i18n.t('filter.popular-tags')}

        <LabelsList
          labels={this.props.popularLabels}
          onLabelClick={this.onPopularLabelClick}/>

{i18n.t('filter.by-tag')}

        <AutoSuggestedTags
          value={this.state.label}
          labels={this.labelsPoolToTags()}
          placeholder={i18n.t('task.search-by-tags')}
          onChange={this.handleLabelChange}/>

        {downloadCSV}

    );
  }

  handleLabelChange(label) {
    this.setState({label});
    this.props.onLabelChange(label);
  }
}

TaskFilters.propTypes = {
  onLabelChange: PropTypes.func.isRequired,
  selectedProject: PropTypes.object,
  popularLabels: PropTypes.array,
  labelsPool: PropTypes.object.isRequired,
  generateCSV: PropTypes.func.isRequired,
  isAdmin: PropTypes.bool.isRequired,
  userDefaultProject: PropTypes.string,
};

export default TaskFilters;
<|endoftext|>


<commit_before>import React, { Component, PropTypes } from 'react';
// import ErrorStackParser from "error-stack-parser";

import {getStackFrames} from "./react-error-overlay/utils/getStackFrames";
import StackTrace from "./react-error-overlay/containers/StackTrace";

export default class StackTraceTab extends Component {
    constructor(props) {
        super(props);

        this.state = {
            stackFrames : []
        };
    }
    componentDidMount() {
        //console.log("StackTraceTab mounted");
        this.checkForStackTrace();
    }

    componentDidUpdate(prevProps) {
        const {action, actions} = prevProps;

        if(action !== this.props.action || actions !== this.props.actions) {
            this.checkForStackTrace();
        }
    }

    checkForStackTrace() {
        const {action, actions : liftedActionsById} = this.props;

        if(!action) {
            return;
        }

        const liftedActions = Object.values(liftedActionsById);
        const liftedAction = liftedActions.find(liftedAction => liftedAction.action === action);

        if(liftedAction && typeof liftedAction.stack === 'string') {
            const deserializedError = Object.assign(new Error(), {stack: liftedAction.stack});

            getStackFrames(deserializedError)
                .then(stackFrames => {
                    console.log("Stack frames: ", stackFrames);
                    this.setState({stackFrames, currentError : deserializedError});
                })
        }
        else {
            this.setState({stackFrames : []})
        }
    }

    /*
    onStackFrameClicked = (i) => {
        const stackFrame = this.state.stackFrames[i];

        if(stackFrame) {
            const parsedFramesNoSourcemaps = ErrorStackParser.parse(this.state.currentError)
            console.log("Parsed stack frames: ", parsedFramesNoSourcemaps);

            if(chrome && chrome.devtools.panels.openResource) {
                const frameWithoutSourcemap = parsedFramesNoSourcemaps[i];
                const {fileName, lineNumber} = frameWithoutSourcemap;
                console.log("Parsed stack frame: ", stackFrame);
                console.log("Original stack frame: ", frameWithoutSourcemap);

                const adjustedLineNumber = Math.max(lineNumber - 1, 0);

                chrome.devtools.panels.openResource(fileName, adjustedLineNumber, (...callbackArgs) => {
                    console.log("openResource callback args: ", callbackArgs);
                    //console.log("Testing");
                });
            }
        }
    }
    */

    onStackLocationClicked = (fileLocation = {}) => {
        //console.log("Stack location args: ", ...args);

        // const parsedFramesNoSourcemaps = ErrorStackParser.parse(this.state.currentError)
        //console.log("Parsed stack frames: ", parsedFramesNoSourcemaps);

        const {fileName, lineNumber} = fileLocation;

        if(fileName && lineNumber) {
            const matchingStackFrame = this.state.stackFrames.find(stackFrame => {
                const matches = (
                    (stackFrame._originalFileName === fileName && stackFrame._originalLineNumber === lineNumber) ||
                    (stackFrame.fileName === fileName && stackFrame.lineNumber === lineNumber)
                );
                return matches;
            })

            //console.log("Matching stack frame: ", matchingStackFrame);

            if(matchingStackFrame) {
                /*
                const frameIndex = this.state.stackFrames.indexOf(matchingStackFrame);
                const originalStackFrame = parsedFramesNoSourcemaps[frameIndex];
                console.log("Original stack frame: ", originalStackFrame);
                */
                const adjustedLineNumber = Math.max(lineNumber - 1, 0);

                chrome.devtools.panels.openResource(fileName, adjustedLineNumber, (result) => {
                    //console.log("openResource callback args: ", callbackArgs);
                    //console.log("Testing");
                    if(result.isError) {
                        const {fileName : finalFileName, lineNumber : finalLineNumber} = matchingStackFrame;
                        const adjustedLineNumber = Math.max(finalLineNumber - 1, 0);
                        chrome.devtools.panels.openResource(finalFileName, adjustedLineNumber, (result) => {
                            //console.log("openResource result: ", result);
                        });
                    }
                });
            }
        }

    }

    render() {
        const {stackFrames} = this.state;

        return (

Dispatched Action Stack Trace

                    <StackTrace
                        stackFrames={stackFrames}
                        errorName={"N/A"}
                        contextSize={3}
                        editorHandler={this.onStackLocationClicked}
                    />

        )
    }
}<commit_msg>Remove unused code referred to `error-stack-parser`<commit_after>import React, { Component, PropTypes } from 'react';

import {getStackFrames} from "./react-error-overlay/utils/getStackFrames";
import StackTrace from "./react-error-overlay/containers/StackTrace";

export default class StackTraceTab extends Component {
    constructor(props) {
        super(props);

        this.state = {
            stackFrames : []
        };
    }
    componentDidMount() {
        //console.log("StackTraceTab mounted");
        this.checkForStackTrace();
    }

    componentDidUpdate(prevProps) {
        const {action, actions} = prevProps;

        if(action !== this.props.action || actions !== this.props.actions) {
            this.checkForStackTrace();
        }
    }

    checkForStackTrace() {
        const {action, actions : liftedActionsById} = this.props;

        if(!action) {
            return;
        }

        const liftedActions = Object.values(liftedActionsById);
        const liftedAction = liftedActions.find(liftedAction => liftedAction.action === action);

        if(liftedAction && typeof liftedAction.stack === 'string') {
            const deserializedError = Object.assign(new Error(), {stack: liftedAction.stack});

            getStackFrames(deserializedError)
                .then(stackFrames => {
                    console.log("Stack frames: ", stackFrames);
                    this.setState({stackFrames, currentError : deserializedError});
                })
        }
        else {
            this.setState({stackFrames : []})
        }
    }

    onStackLocationClicked = (fileLocation = {}) => {
        //console.log("Stack location args: ", ...args);

        const {fileName, lineNumber} = fileLocation;

        if(fileName && lineNumber) {
            const matchingStackFrame = this.state.stackFrames.find(stackFrame => {
                const matches = (
                    (stackFrame._originalFileName === fileName && stackFrame._originalLineNumber === lineNumber) ||
                    (stackFrame.fileName === fileName && stackFrame.lineNumber === lineNumber)
                );
                return matches;
            })

            //console.log("Matching stack frame: ", matchingStackFrame);

            if(matchingStackFrame) {
                /*
                const frameIndex = this.state.stackFrames.indexOf(matchingStackFrame);
                const originalStackFrame = parsedFramesNoSourcemaps[frameIndex];
                console.log("Original stack frame: ", originalStackFrame);
                */
                const adjustedLineNumber = Math.max(lineNumber - 1, 0);

                chrome.devtools.panels.openResource(fileName, adjustedLineNumber, (result) => {
                    //console.log("openResource callback args: ", callbackArgs);
                    //console.log("Testing");
                    if(result.isError) {
                        const {fileName : finalFileName, lineNumber : finalLineNumber} = matchingStackFrame;
                        const adjustedLineNumber = Math.max(finalLineNumber - 1, 0);
                        chrome.devtools.panels.openResource(finalFileName, adjustedLineNumber, (result) => {
                            //console.log("openResource result: ", result);
                        });
                    }
                });
            }
        }

    }

    render() {
        const {stackFrames} = this.state;

        return (

Dispatched Action Stack Trace

                    <StackTrace
                        stackFrames={stackFrames}
                        errorName={"N/A"}
                        contextSize={3}
                        editorHandler={this.onStackLocationClicked}
                    />

        )
    }
}<|endoftext|>


<commit_before>import React from 'react';
import { shallow } from 'enzyme';
import Loader from '../../Loader/Loader'
import Catalog from '../Catalog'
import CatalogPreview from '../CatalogPreview/CatalogPreview'
import { mountWithContext } from '../../../test/jsdom-setup'

import catalog from '../../fetch/__test__/catalog.json'
import metrics from '../../fetch/__test__/metrics.json'
jest.mock('../../fetch/fetch');

describe('<Catalog />', () => {

  describe('When all goes well', () => {
    let wrapper

    beforeEach(() => {
      wrapper = shallow(<Catalog catalog={catalog} />)
    })

    it('should display the name of the catalog', () => {
      expect(wrapper.contains(catalog.name)).toBe(true);
    });

    it('should display <CatalogPreview />', () => {
      const wrapper = mountWithContext(<Catalog catalog={catalog} />)
      const catalogPreview = <CatalogPreview metrics={metrics} />

      return wrapper
        .instance()
        .componentWillMount()
        .then(() => {
          expect(wrapper.contains(catalogPreview)).toBe(true)
        })
    });

  })

  describe('fetch metrics', () => {
    it('should set metrics', () => {
      const wrapper = mountWithContext(<Catalog catalog={catalog} />)

      return wrapper
        .instance()
        .componentWillMount()
        .then(() => expect(wrapper.state('metrics')).toEqual(metrics))
    });

    it('should display a loading', () => {
      const loader = mountWithContext(<Loader component={

} value={undefined} />)
      const wrapper = mountWithContext(<Catalog catalog={catalog} />)

      expect(wrapper.html()).toContain(loader.html())
    });
  })

})
<commit_msg>Remove semicolon<commit_after>import React from 'react'
import { shallow } from 'enzyme'
import Loader from '../../Loader/Loader'
import Catalog from '../Catalog'
import CatalogPreview from '../CatalogPreview/CatalogPreview'
import { mountWithContext } from '../../../test/jsdom-setup'

import catalog from '../../fetch/__test__/catalog.json'
import metrics from '../../fetch/__test__/metrics.json'
jest.mock('../../fetch/fetch')

describe('<Catalog />', () => {

  describe('When all goes well', () => {
    let wrapper

    beforeEach(() => {
      wrapper = shallow(<Catalog catalog={catalog} />)
    })

    it('should display the name of the catalog', () => {
      expect(wrapper.contains(catalog.name)).toBe(true)
    })

    it('should display <CatalogPreview />', () => {
      const wrapper = mountWithContext(<Catalog catalog={catalog} />)
      const catalogPreview = <CatalogPreview metrics={metrics} />

      return wrapper
        .instance()
        .componentWillMount()
        .then(() => {
          expect(wrapper.contains(catalogPreview)).toBe(true)
        })
    })

  })

  describe('fetch metrics', () => {
    it('should set metrics', () => {
      const wrapper = mountWithContext(<Catalog catalog={catalog} />)

      return wrapper
        .instance()
        .componentWillMount()
        .then(() => expect(wrapper.state('metrics')).toEqual(metrics))
    })

    it('should display a loading', () => {
      const loader = mountWithContext(<Loader component={

} value={undefined} />)
      const wrapper = mountWithContext(<Catalog catalog={catalog} />)

      expect(wrapper.html()).toContain(loader.html())
    })
  })

})
<|endoftext|>


<commit_before>import React from 'react';
import { Card, CardActions, CardHeader, CardMedia, CardTitle, CardText } from 'material-ui/Card';
import Dialog from 'material-ui/Dialog';
import DishPage from 'containers/Restaurant/DishPage';
import DishDeleteButton from '../Business/DishDeleteButton';

const styles = {
  card:{
    position: 'relative',
    height: '384px',
    width: '250px',
    backgroundColor: '#f6f6f6',
    margin: '10px 5px 10px 5px',
    CardImg:{
      width: '100%',
      height: '250px',
      overflow: 'hidden'
    }
  }
}

class DishCard extends React.PureComponent{
  constructor(){
    super();
    this.state = {
      cardDepth: 0,
      modalIsOpen: false
    }

    this.onMouseEnter = this.onMouseEnter.bind(this);
    this.onMouseLeave = this.onMouseLeave.bind(this);
    this.handleOpen = this.handleOpen.bind(this);
    this.handleClose = this.handleClose.bind(this);
  }

  handleOpen(){
    this.setState({
      modalIsOpen: true
    });
  }

  handleClose(){
    this.setState({
      modalIsOpen: false
    });
  }

  onMouseEnter(){
    this.setState({
      cardDepth: 2
    })
  }

  onMouseLeave(){
    this.setState({
      cardDepth: 0
    })
  }

  deleteDish(e) {
    const { dish } = this.state;
    const { dispatch } = this.props;
    e.preventDefault();
    e.stopPropagation();
    dispatch(deleteDish(dish));
    this.props.closeModal();
  }

  render(){
    const {dish} = this.props;
    return(
      <Card style={styles.card}  zDepth={this.state.cardDepth} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave} onTouchTap={this.handleOpen}>
        <DishDeleteButton {...this.props}/>
        <CardMedia style={styles.card.CardImg}>

        </CardMedia>
        <CardTitle title={dish.name} titleColor="#757575" titleStyle={{fontSize: '24px', fontWeight: '100'}}/>
        <CardText color="#757575">
          {dish.description}
        </CardText>
        <Dialog
          modal={false}
          open={this.state.modalIsOpen}
          onRequestClose={this.handleClose}
          autoScrollBodyContent={true}
          contentStyle={{width: '80%', maxWidth: 'none'}}
        >
          <DishPage closeModal={this.handleClose} {...this.props}/>
        </Dialog>
      </Card>
    )
  }
};

export default DishCard;
<commit_msg>dish delete button now only visible to business account<commit_after>import React from 'react';
import { Card, CardActions, CardHeader, CardMedia, CardTitle, CardText } from 'material-ui/Card';
import Dialog from 'material-ui/Dialog';
import DishPage from 'containers/Restaurant/DishPage';
import DishDeleteButton from '../Business/DishDeleteButton';

const styles = {
  card:{
    position: 'relative',
    height: '384px',
    width: '250px',
    backgroundColor: '#f6f6f6',
    margin: '10px 5px 10px 5px',
    CardImg:{
      width: '100%',
      height: '250px',
      overflow: 'hidden'
    }
  }
}

class DishCard extends React.PureComponent{
  constructor(){
    super();
    this.state = {
      cardDepth: 0,
      modalIsOpen: false
    }

    this.onMouseEnter = this.onMouseEnter.bind(this);
    this.onMouseLeave = this.onMouseLeave.bind(this);
    this.handleOpen = this.handleOpen.bind(this);
    this.handleClose = this.handleClose.bind(this);
  }

  handleOpen(){
    this.setState({
      modalIsOpen: true
    });
  }

  handleClose(){
    this.setState({
      modalIsOpen: false
    });
  }

  onMouseEnter(){
    this.setState({
      cardDepth: 2
    })
  }

  onMouseLeave(){
    this.setState({
      cardDepth: 0
    })
  }

  deleteDish(e) {
    const { dish } = this.state;
    const { dispatch } = this.props;
    e.preventDefault();
    e.stopPropagation();
    dispatch(deleteDish(dish));
    this.props.closeModal();
  }

  render(){
    const {dish, auth} = this.props;
    return(
      <Card style={styles.card}  zDepth={this.state.cardDepth} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave} onTouchTap={this.handleOpen}>
        {auth.user.role === "business" && (<DishDeleteButton {...this.props}/>)}
        <CardMedia style={styles.card.CardImg}>

        </CardMedia>
        <CardTitle title={dish.name} titleColor="#757575" titleStyle={{fontSize: '24px', fontWeight: '100'}}/>
        <CardText color="#757575">
          {dish.description}
        </CardText>
        <Dialog
          modal={false}
          open={this.state.modalIsOpen}
          onRequestClose={this.handleClose}
          autoScrollBodyContent={true}
          contentStyle={{width: '80%', maxWidth: 'none'}}
        >
          <DishPage closeModal={this.handleClose} {...this.props}/>
        </Dialog>
      </Card>
    )
  }
};

export default DishCard;
<|endoftext|>