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

var igv = (function (igv) {

    var BAM_MAGIC = 21840194;
    var BAI_MAGIC = 21578050;
    var SECRET_DECODER = ['=', 'A', 'C', 'x', 'G', 'x', 'x', 'x', 'T', 'x', 'x', 'x', 'x', 'x', 'x', 'N'];
    var CIGAR_DECODER = ['M', 'I', 'D', 'N', 'S', 'H', 'P', '=', 'X', '?', '?', '?', '?', '?', '?', '?'];
    var READ_PAIRED_FLAG = 0x1;
    var PROPER_PAIR_FLAG = 0x2;
    var READ_UNMAPPED_FLAG = 0x4;
    var MATE_UNMAPPED_FLAG = 0x8;
    var READ_STRAND_FLAG = 0x10;
    var MATE_STRAND_FLAG = 0x20;
    var FIRST_OF_PAIR_FLAG = 0x40;
    var SECOND_OF_PAIR_FLAG = 0x80;
    var SECONDARY_ALIGNMNET_FLAG = 0x100;
    var READ_FAILS_VENDOR_QUALITY_CHECK_FLAG = 0x200;
    var DUPLICATE_READ_FLAG = 0x400;
    var SUPPLEMENTARY_ALIGNMENT_FLAG = 0x800;

    /**
     * readName
     * chr
     * cigar
     * lengthOnRef
     * start
     * seq
     * qual
     * mq
     * strand
     * blocks
     */

    igv.BamAlignment = function () {
        this.hidden = false;
    }

    igv.BamAlignment.prototype.isMapped = function () {
        return (this.flags & READ_UNMAPPED_FLAG) == 0;
    }

    igv.BamAlignment.prototype.isPaired = function () {
        return (this.flags & READ_PAIRED_FLAG) != 0;
    }

    igv.BamAlignment.prototype.isProperPair = function () {
        return (this.flags & PROPER_PAIR_FLAG) != 0;
    }

    igv.BamAlignment.prototype.isFirstOfPair = function () {
        return (this.flags & FIRST_OF_PAIR_FLAG) != 0;
    }

    igv.BamAlignment.prototype.isSecondOfPair = function () {
        return (this.flags & SECOND_OF_PAIR_FLAG) != 0;
    }

    igv.BamAlignment.prototype.isSecondary = function () {
        return (this.flags & SECONDARY_ALIGNMNET_FLAG) != 0;
    }

    igv.BamAlignment.prototype.isSupplementary = function () {
        return (this.flags & SUPPLEMENTARY_ALIGNMENT_FLAG) != 0;
    }

    igv.BamAlignment.prototype.isFailsVendorQualityCheck = function () {
        return (this.flags & READ_FAILS_VENDOR_QUALITY_CHECK_FLAG) != 0;
    }

    igv.BamAlignment.prototype.isDuplicate = function () {
        return (this.flags & DUPLICATE_READ_FLAG) != 0;
    }

    igv.BamAlignment.prototype.isMateMapped = function () {
        return (this.flags & MATE_UNMAPPED_FLAG) == 0;
    }

    igv.BamAlignment.prototype.isNegativeStrand = function () {
        return (this.flags & READ_STRAND_FLAG) != 0;
    }

    igv.BamAlignment.prototype.isMateNegativeStrand = function () {
        return (this.flags & MATE_STRAND_FLAG) != 0;
    }

    igv.BamAlignment.prototype.tags = function () {

        function decodeTags(ba) {

            var p = 0,
                len = ba.length,
                tags = {};

            while (p < len) {
                var tag = String.fromCharCode(ba[p]) + String.fromCharCode(ba[p + 1]);
                var type = String.fromCharCode(ba[p + 2]);
                var value;

                if (type == 'A') {
                    value = String.fromCharCode(ba[p + 3]);
                    p += 4;
                } else if (type === 'i' || type === 'I') {
                    value = readInt(ba, p + 3);
                    p += 7;
                } else if (type === 'c' || type === 'C') {
                    value = ba[p + 3];
                    p += 4;
                } else if (type === 's' || type === 'S') {
                    value = readShort(ba, p + 3);
                    p += 5;
                } else if (type === 'f') {
                    value = readFloat(ba, p + 3);
                    p += 7;
                } else if (type === 'Z') {
                    p += 3;
                    value = '';
                    for (; ;) {
                        var cc = ba[p++];
                        if (cc === 0) {
                            break;
                        } else {
                            value += String.fromCharCode(cc);
                        }
                    }
                } else {
                    //'Unknown type ' + type;
                    value = 'Error unknown type: ' + type;
                    tags[tag] = value;
                    break;
                }
                tags[tag] = value;
            }
            return tags;
        }

        if (!this.tagDict) {
            if (this.tagBA) {
                this.tagDict = decodeTags(this.tagBA);
                this.tagBA = undefined;
            } else {
                this.tagDict = {};  // Mark so we don't try again.  The record has not tags
            }
        }
        return this.tagDict;

    }

    igv.BamAlignment.prototype.popupData = function (genomicLocation) {

        // if the user clicks on a base next to an insertion, show just the
        // inserted bases in a popup (like in desktop IGV).
        var nameValues = [],
            isFirst,
            tagDict;

        if(this.insertions) {
            for(var i = 0; i < this.insertions.length; i += 1) {
                var ins_start = this.insertions[i].start;
                if(genomicLocation == ins_start || genomicLocation == ins_start - 1) {
                    nameValues.push({name: 'Insertion', value: this.insertions[i].seq });
                    nameValues.push({name: 'Location', value: ins_start });
                    return nameValues;
                }
            }
        }

        nameValues.push({ name: 'Read Name', value: this.readName });

        // Sample
        // Read group
        nameValues.push("
");

        // Add 1 to genomic location to map from 0-based computer units to user-based units
        nameValues.push({ name: 'Alignment Start', value: igv.numberFormatter(1 + this.start), borderTop: true });

        nameValues.push({ name: 'Read Strand', value: (true === this.strand ? '(+)' : '(-)'), borderTop: true });
        nameValues.push({ name: 'Cigar', value: this.cigar });
        nameValues.push({ name: 'Mapped', value: yesNo(this.isMapped()) });
        nameValues.push({ name: 'Mapping Quality', value: this.mq });
        nameValues.push({ name: 'Secondary', value: yesNo(this.isSecondary()) });
        nameValues.push({ name: 'Supplementary', value: yesNo(this.isSupplementary()) });
        nameValues.push({ name: 'Duplicate', value: yesNo(this.isDuplicate()) });
        nameValues.push({ name: 'Failed QC', value: yesNo(this.isFailsVendorQualityCheck()) });

        if (this.isPaired()) {
            nameValues.push("
");
            nameValues.push({ name: 'First in Pair', value: !this.isSecondOfPair(), borderTop: true });
            nameValues.push({ name: 'Mate is Mapped', value: yesNo(this.isMateMapped()) });
            if (this.isMapped()) {
                nameValues.push({ name: 'Mate Chromosome', value: this.mate.chr });
                nameValues.push({ name: 'Mate Start', value: (this.mate.position + 1)});
                nameValues.push({ name: 'Mate Strand', value: (true === this.mate.strand ? '(+)' : '(-)')});
                nameValues.push({ name: 'Insert Size', value: this.fragmentLength });
                // Mate Start
                // Mate Strand
                // Insert Size
            }
            // First in Pair
            // Pair Orientation

        }

        nameValues.push("
");
        tagDict = this.tags();
        isFirst = true;
        for (var key in tagDict) {

            if (tagDict.hasOwnProperty(key)) {

                if (isFirst) {
                    nameValues.push({ name: key, value: tagDict[key], borderTop: true });
                    isFirst = false;
                } else {
                    nameValues.push({ name: key, value: tagDict[key] });
                }

            }
        }

        return nameValues;

        function yesNo(bool) {
            return bool ? 'Yes' : 'No';
        }
    }

    function readInt(ba, offset) {
        return (ba[offset + 3] << 24) | (ba[offset + 2] << 16) | (ba[offset + 1] << 8) | (ba[offset]);
    }

    function readShort(ba, offset) {
        return (ba[offset + 1] << 8) | (ba[offset]);
    }

    function readFloat(ba, offset) {

        var dataView = new DataView(ba.buffer),
            littleEndian = true;

        return dataView.getFloat32(offset, littleEndian);

    }

    igv.BamFilter = function (options) {
        if (!options) options = {};
        this.vendorFailed = options.vendorFailed === undefined ? true : options.vendorFailed;
        this.duplicates = options.duplicates === undefined ? true : options.duplicates;
        this.secondary = options.secondary || false;
        this.supplementary = options.supplementary || false;
        this.mqThreshold = options.mqThreshold === undefined ? 0 : options.mqThreshold;
    }

    igv.BamFilter.prototype.pass = function (alignment) {

        if (this.vendorFailed && alignment.isFailsVendorQualityCheck()) return false;
        if (this.duplicates && alignment.isDuplicate()) return false;
        if (this.secondary && alignment.isSecondary()) return false;
        if (this.supplementary && alignment.isSupplementary()) return false;
        if (alignment.mq < this.mqThreshold) return false;

        return true;

    }

    return igv;

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

var igv = (function (igv) {

    var BAM_MAGIC = 21840194;
    var BAI_MAGIC = 21578050;
    var SECRET_DECODER = ['=', 'A', 'C', 'x', 'G', 'x', 'x', 'x', 'T', 'x', 'x', 'x', 'x', 'x', 'x', 'N'];
    var CIGAR_DECODER = ['M', 'I', 'D', 'N', 'S', 'H', 'P', '=', 'X', '?', '?', '?', '?', '?', '?', '?'];
    var READ_PAIRED_FLAG = 0x1;
    var PROPER_PAIR_FLAG = 0x2;
    var READ_UNMAPPED_FLAG = 0x4;
    var MATE_UNMAPPED_FLAG = 0x8;
    var READ_STRAND_FLAG = 0x10;
    var MATE_STRAND_FLAG = 0x20;
    var FIRST_OF_PAIR_FLAG = 0x40;
    var SECOND_OF_PAIR_FLAG = 0x80;
    var SECONDARY_ALIGNMNET_FLAG = 0x100;
    var READ_FAILS_VENDOR_QUALITY_CHECK_FLAG = 0x200;
    var DUPLICATE_READ_FLAG = 0x400;
    var SUPPLEMENTARY_ALIGNMENT_FLAG = 0x800;

    /**
     * readName
     * chr
     * cigar
     * lengthOnRef
     * start
     * seq
     * qual
     * mq
     * strand
     * blocks
     */

    igv.BamAlignment = function () {
        this.hidden = false;
    }

    igv.BamAlignment.prototype.isMapped = function () {
        return (this.flags & READ_UNMAPPED_FLAG) == 0;
    }

    igv.BamAlignment.prototype.isPaired = function () {
        return (this.flags & READ_PAIRED_FLAG) != 0;
    }

    igv.BamAlignment.prototype.isProperPair = function () {
        return (this.flags & PROPER_PAIR_FLAG) != 0;
    }

    igv.BamAlignment.prototype.isFirstOfPair = function () {
        return (this.flags & FIRST_OF_PAIR_FLAG) != 0;
    }

    igv.BamAlignment.prototype.isSecondOfPair = function () {
        return (this.flags & SECOND_OF_PAIR_FLAG) != 0;
    }

    igv.BamAlignment.prototype.isSecondary = function () {
        return (this.flags & SECONDARY_ALIGNMNET_FLAG) != 0;
    }

    igv.BamAlignment.prototype.isSupplementary = function () {
        return (this.flags & SUPPLEMENTARY_ALIGNMENT_FLAG) != 0;
    }

    igv.BamAlignment.prototype.isFailsVendorQualityCheck = function () {
        return (this.flags & READ_FAILS_VENDOR_QUALITY_CHECK_FLAG) != 0;
    }

    igv.BamAlignment.prototype.isDuplicate = function () {
        return (this.flags & DUPLICATE_READ_FLAG) != 0;
    }

    igv.BamAlignment.prototype.isMateMapped = function () {
        return (this.flags & MATE_UNMAPPED_FLAG) == 0;
    }

    igv.BamAlignment.prototype.isNegativeStrand = function () {
        return (this.flags & READ_STRAND_FLAG) != 0;
    }

    igv.BamAlignment.prototype.isMateNegativeStrand = function () {
        return (this.flags & MATE_STRAND_FLAG) != 0;
    }

    igv.BamAlignment.prototype.tags = function () {

        function decodeTags(ba) {

            var p = 0,
                len = ba.length,
                tags = {};

            while (p < len) {
                var tag = String.fromCharCode(ba[p]) + String.fromCharCode(ba[p + 1]);
                var type = String.fromCharCode(ba[p + 2]);
                var value;

                if (type == 'A') {
                    value = String.fromCharCode(ba[p + 3]);
                    p += 4;
                } else if (type === 'i' || type === 'I') {
                    value = readInt(ba, p + 3);
                    p += 7;
                } else if (type === 'c' || type === 'C') {
                    value = ba[p + 3];
                    p += 4;
                } else if (type === 's' || type === 'S') {
                    value = readShort(ba, p + 3);
                    p += 5;
                } else if (type === 'f') {
                    value = readFloat(ba, p + 3);
                    p += 7;
                } else if (type === 'Z') {
                    p += 3;
                    value = '';
                    for (; ;) {
                        var cc = ba[p++];
                        if (cc === 0) {
                            break;
                        } else {
                            value += String.fromCharCode(cc);
                        }
                    }
                } else {
                    //'Unknown type ' + type;
                    value = 'Error unknown type: ' + type;
                    tags[tag] = value;
                    break;
                }
                tags[tag] = value;
            }
            return tags;
        }

        if (!this.tagDict) {
            if (this.tagBA) {
                this.tagDict = decodeTags(this.tagBA);
                this.tagBA = undefined;
            } else {
                this.tagDict = {};  // Mark so we don't try again.  The record has not tags
            }
        }
        return this.tagDict;

    }

    igv.BamAlignment.prototype.popupData = function (genomicLocation) {

        // if the user clicks on a base next to an insertion, show just the
        // inserted bases in a popup (like in desktop IGV).
        var nameValues = [],
            isFirst,
            tagDict;

        if(this.insertions) {
            for(var i = 0; i < this.insertions.length; i += 1) {
                var ins_start = this.insertions[i].start;
                if(genomicLocation == ins_start || genomicLocation == ins_start - 1) {
                    nameValues.push({name: 'Insertion', value: this.insertions[i].seq });
                    nameValues.push({name: 'Location', value: ins_start });
                    return nameValues;
                }
            }
        }

        nameValues.push({ name: 'Read Name', value: this.readName });

        // Sample
        // Read group
        nameValues.push("
");

        // Add 1 to genomic location to map from 0-based computer units to user-based units
        nameValues.push({ name: 'Alignment Start', value: igv.numberFormatter(1 + this.start), borderTop: true });

        nameValues.push({ name: 'Read Strand', value: (true === this.strand ? '(+)' : '(-)'), borderTop: true });
        nameValues.push({ name: 'Cigar', value: this.cigar });
        nameValues.push({ name: 'Mapped', value: yesNo(this.isMapped()) });
        nameValues.push({ name: 'Mapping Quality', value: this.mq });
        nameValues.push({ name: 'Secondary', value: yesNo(this.isSecondary()) });
        nameValues.push({ name: 'Supplementary', value: yesNo(this.isSupplementary()) });
        nameValues.push({ name: 'Duplicate', value: yesNo(this.isDuplicate()) });
        nameValues.push({ name: 'Failed QC', value: yesNo(this.isFailsVendorQualityCheck()) });

        if (this.isPaired()) {
            nameValues.push("
");
            nameValues.push({ name: 'First in Pair', value: !this.isSecondOfPair(), borderTop: true });
            nameValues.push({ name: 'Mate is Mapped', value: yesNo(this.isMateMapped()) });
            if (this.isMateMapped()) {
                nameValues.push({ name: 'Mate Chromosome', value: this.mate.chr });
                nameValues.push({ name: 'Mate Start', value: (this.mate.position + 1)});
                nameValues.push({ name: 'Mate Strand', value: (true === this.mate.strand ? '(+)' : '(-)')});
                nameValues.push({ name: 'Insert Size', value: this.fragmentLength });
                // Mate Start
                // Mate Strand
                // Insert Size
            }
            // First in Pair
            // Pair Orientation

        }

        nameValues.push("
");
        tagDict = this.tags();
        isFirst = true;
        for (var key in tagDict) {

            if (tagDict.hasOwnProperty(key)) {

                if (isFirst) {
                    nameValues.push({ name: key, value: tagDict[key], borderTop: true });
                    isFirst = false;
                } else {
                    nameValues.push({ name: key, value: tagDict[key] });
                }

            }
        }

        return nameValues;

        function yesNo(bool) {
            return bool ? 'Yes' : 'No';
        }
    }

    function readInt(ba, offset) {
        return (ba[offset + 3] << 24) | (ba[offset + 2] << 16) | (ba[offset + 1] << 8) | (ba[offset]);
    }

    function readShort(ba, offset) {
        return (ba[offset + 1] << 8) | (ba[offset]);
    }

    function readFloat(ba, offset) {

        var dataView = new DataView(ba.buffer),
            littleEndian = true;

        return dataView.getFloat32(offset, littleEndian);

    }

    igv.BamFilter = function (options) {
        if (!options) options = {};
        this.vendorFailed = options.vendorFailed === undefined ? true : options.vendorFailed;
        this.duplicates = options.duplicates === undefined ? true : options.duplicates;
        this.secondary = options.secondary || false;
        this.supplementary = options.supplementary || false;
        this.mqThreshold = options.mqThreshold === undefined ? 0 : options.mqThreshold;
    }

    igv.BamFilter.prototype.pass = function (alignment) {

        if (this.vendorFailed && alignment.isFailsVendorQualityCheck()) return false;
        if (this.duplicates && alignment.isDuplicate()) return false;
        if (this.secondary && alignment.isSecondary()) return false;
        if (this.supplementary && alignment.isSupplementary()) return false;
        if (alignment.mq < this.mqThreshold) return false;

        return true;

    }

    return igv;

})(igv || {});
<|endoftext|>


<commit_before>// @flow

import React from 'react'
import {StyleSheet, SectionList} from 'react-native'
import * as c from '../../components/colors'
import {connect} from 'react-redux'
import {updateMenuFilters} from '../../../flux'
import type {TopLevelViewPropsType} from '../../types'
import type momentT from 'moment'
import type {
  MenuItemType,
  MasterCorIconMapType,
  ProcessedMealType,
  MenuItemContainerType,
  StationMenuType,
} from '../types'
import size from 'lodash/size'
import values from 'lodash/values'
import {ListSeparator, ListSectionHeader} from '../../components/list'
import type {FilterType} from '../../components/filter'
import {applyFiltersToItem} from '../../components/filter'
import {NoticeView} from '../../components/notice'
import {FilterMenuToolbar as FilterToolbar} from './filter-menu-toolbar'
import {FoodItemRow} from './food-item-row'
import {chooseMeal} from '../lib/choose-meal'
import {buildFilters} from '../lib/build-filters'

type Props = TopLevelViewPropsType & {
  applyFilters: (filters: FilterType[], item: MenuItemType) => boolean,
  cafeMessage?: ?string,
  filters: FilterType[],
  foodItems: MenuItemContainerType,
  meals: ProcessedMealType[],
  menuCorIcons: MasterCorIconMapType,
  name: string,
  now: momentT,
  onFiltersChange: (f: FilterType[]) => any,
  onRefresh?: ?() => any,
  refreshing?: ?boolean,
}

const styles = StyleSheet.create({
  inner: {
    backgroundColor: c.white,
  },
  message: {
    paddingVertical: 16,
  },
})

const LEFT_MARGIN = 28
const Separator = () => <ListSeparator spacing={{left: LEFT_MARGIN}} />

class FancyMenu extends React.PureComponent<any, Props, void> {
  static defaultProps = {
    applyFilters: applyFiltersToItem,
  }

  componentWillMount() {
    this.updateFilters(this.props)
  }

  componentWillReceiveProps(nextProps: Props) {
    this.updateFilters(nextProps)
  }

  updateFilters = (props: Props) => {
    const {foodItems, menuCorIcons, filters, meals, now} = props

    // prevent ourselves from overwriting the filters from redux on mount
    if (filters.length) {
      return
    }

    const newFilters = buildFilters(values(foodItems), menuCorIcons, meals, now)
    props.onFiltersChange(newFilters)
  }

  areSpecialsFiltered = filters => Boolean(filters.find(this.isSpecialsFilter))
  isSpecialsFilter = f =>
    f.enabled && f.type === 'toggle' && f.spec.label === 'Only Show Specials'

  openFilterView = () => {
    this.props.navigation.navigate('FilterView', {
      title: `Filter ${this.props.name} Menu`,
      pathToFilters: ['menus', this.props.name],
      onChange: filters => this.props.onFiltersChange(filters),
    })
  }

  groupMenuData = (props: Props, stations: Array<StationMenuType>) => {
    const {applyFilters, filters, foodItems} = props

    const derefrenceMenuItems = menu =>
      menu.items
        // Dereference each menu item
        .map(id => foodItems[id])
        // Ensure that the referenced menu items exist,
        // and apply the selected filters to the items in the menu
        .filter(item => item && applyFilters(filters, item))

    const menusWithItems = stations
      // We're grouping the menu items in a [label, Array<items>] tuple.
      .map(menu => [menu.label, derefrenceMenuItems(menu)])
      // We only want to show stations with at least one item in them
      .filter(([_, items]) => items.length)
      // We need to map the tuples into objects for SectionList
      .map(([title, data]) => ({title, data}))

    return menusWithItems
  }

  renderSectionHeader = ({section: {title}}: any) => {
    const {filters, now, meals} = this.props
    const {stations} = chooseMeal(meals, filters, now)
    const menu = stations.find(m => m.label === title)

    return (
      <ListSectionHeader
        title={title}
        subtitle={menu ? menu.note : ''}
        spacing={{left: LEFT_MARGIN}}
      />
    )
  }

  renderItem = ({item}: {item: MenuItemType}) => {
    const specialsFilterEnabled = this.areSpecialsFiltered(this.props.filters)
    return (
      <FoodItemRow
        data={item}
        corIcons={this.props.menuCorIcons}
        badgeSpecials={!specialsFilterEnabled}
        spacing={{left: LEFT_MARGIN}}
      />
    )
  }

  keyExtractor = (item, index) => index.toString()

  render() {
    const {filters, now, meals, cafeMessage} = this.props

    const {label: mealName, stations} = chooseMeal(meals, filters, now)
    const anyFiltersEnabled = filters.some(f => f.enabled)
    const specialsFilterEnabled = this.areSpecialsFiltered(filters)
    const groupedMenuData = this.groupMenuData(this.props, stations)

    let message = 'No items to show.'
    if (cafeMessage) {
      message = cafeMessage
    } else if (specialsFilterEnabled && stations.length === 0) {
      message =
        'No items to show. There may be no specials today. Try changing the filters.'
    } else if (anyFiltersEnabled && !size(groupedMenuData)) {
      message = 'No items to show. Try changing the filters.'
    }

    const messageView = <NoticeView style={styles.message} text={message} />

    const header = (
      <FilterToolbar
        date={now}
        title={mealName}
        filters={filters}
        onPress={this.openFilterView}
      />
    )

    return (
      <SectionList
        ItemSeparatorComponent={Separator}
        ListEmptyComponent={messageView}
        ListHeaderComponent={header}
        data={filters}
        keyExtractor={this.keyExtractor}
        onRefresh={this.props.onRefresh}
        refreshing={this.props.refreshing}
        renderItem={this.renderItem}
        renderSectionHeader={this.renderSectionHeader}
        sections={(groupedMenuData: any)}
        style={styles.inner}
      />
    )
  }
}

const mapState = (state, actualProps: Props) => ({
  filters: state.menus[actualProps.name] || [],
})

const mapDispatch = (dispatch, actualProps: Props) => ({
  onFiltersChange: (filters: FilterType[]) =>
    dispatch(updateMenuFilters(actualProps.name, filters)),
})

export const ConnectedFancyMenu = connect(mapState, mapDispatch)(FancyMenu)
<commit_msg>update FancyMenu to use new redux types<commit_after>// @flow

import React from 'react'
import {StyleSheet, SectionList} from 'react-native'
import * as c from '../../components/colors'
import {connect} from 'react-redux'
import {updateMenuFilters, type ReduxState} from '../../../flux'
import {type TopLevelViewPropsType} from '../../types'
import type momentT from 'moment'
import type {
  MenuItemType,
  MasterCorIconMapType,
  ProcessedMealType,
  MenuItemContainerType,
  StationMenuType,
} from '../types'
import size from 'lodash/size'
import values from 'lodash/values'
import {ListSeparator, ListSectionHeader} from '../../components/list'
import {applyFiltersToItem, type FilterType} from '../../components/filter'
import {NoticeView} from '../../components/notice'
import {FilterMenuToolbar as FilterToolbar} from './filter-menu-toolbar'
import {FoodItemRow} from './food-item-row'
import {chooseMeal} from '../lib/choose-meal'
import {buildFilters} from '../lib/build-filters'

type ReactProps = TopLevelViewPropsType & {
  cafeMessage?: ?string,
  foodItems: MenuItemContainerType,
  meals: ProcessedMealType[],
  menuCorIcons: MasterCorIconMapType,
  name: string,
  now: momentT,
  onRefresh?: ?() => any,
  refreshing?: ?boolean,
}

type ReduxDispatchProps = {
  onFiltersChange: (f: FilterType[]) => any,
}

type ReduxStateProps = {
  filters: FilterType[],
}

type DefaultProps = {
  applyFilters: (filters: FilterType[], item: MenuItemType) => boolean,
}

type Props = ReactProps & ReduxStateProps & ReduxDispatchProps & DefaultProps

const styles = StyleSheet.create({
  inner: {
    backgroundColor: c.white,
  },
  message: {
    paddingVertical: 16,
  },
})

const LEFT_MARGIN = 28
const Separator = () => <ListSeparator spacing={{left: LEFT_MARGIN}} />

class FancyMenu extends React.PureComponent<any, Props, void> {
  static defaultProps = {
    applyFilters: applyFiltersToItem,
  }

  componentWillMount() {
    this.updateFilters(this.props)
  }

  componentWillReceiveProps(nextProps: Props) {
    this.updateFilters(nextProps)
  }

  updateFilters = (props: Props) => {
    const {foodItems, menuCorIcons, filters, meals, now} = props

    // prevent ourselves from overwriting the filters from redux on mount
    if (filters.length) {
      return
    }

    const newFilters = buildFilters(values(foodItems), menuCorIcons, meals, now)
    props.onFiltersChange(newFilters)
  }

  areSpecialsFiltered = filters => Boolean(filters.find(this.isSpecialsFilter))
  isSpecialsFilter = f =>
    f.enabled && f.type === 'toggle' && f.spec.label === 'Only Show Specials'

  openFilterView = () => {
    this.props.navigation.navigate('FilterView', {
      title: `Filter ${this.props.name} Menu`,
      pathToFilters: ['menus', this.props.name],
      onChange: filters => this.props.onFiltersChange(filters),
    })
  }

  groupMenuData = (props: Props, stations: Array<StationMenuType>) => {
    const {applyFilters, filters, foodItems} = props

    const derefrenceMenuItems = menu =>
      menu.items
        // Dereference each menu item
        .map(id => foodItems[id])
        // Ensure that the referenced menu items exist,
        // and apply the selected filters to the items in the menu
        .filter(item => item && applyFilters(filters, item))

    const menusWithItems = stations
      // We're grouping the menu items in a [label, Array<items>] tuple.
      .map(menu => [menu.label, derefrenceMenuItems(menu)])
      // We only want to show stations with at least one item in them
      .filter(([_, items]) => items.length)
      // We need to map the tuples into objects for SectionList
      .map(([title, data]) => ({title, data}))

    return menusWithItems
  }

  renderSectionHeader = ({section: {title}}: any) => {
    const {filters, now, meals} = this.props
    const {stations} = chooseMeal(meals, filters, now)
    const menu = stations.find(m => m.label === title)

    return (
      <ListSectionHeader
        title={title}
        subtitle={menu ? menu.note : ''}
        spacing={{left: LEFT_MARGIN}}
      />
    )
  }

  renderItem = ({item}: {item: MenuItemType}) => {
    const specialsFilterEnabled = this.areSpecialsFiltered(this.props.filters)
    return (
      <FoodItemRow
        data={item}
        corIcons={this.props.menuCorIcons}
        badgeSpecials={!specialsFilterEnabled}
        spacing={{left: LEFT_MARGIN}}
      />
    )
  }

  keyExtractor = (item, index) => index.toString()

  render() {
    const {filters, now, meals, cafeMessage} = this.props

    const {label: mealName, stations} = chooseMeal(meals, filters, now)
    const anyFiltersEnabled = filters.some(f => f.enabled)
    const specialsFilterEnabled = this.areSpecialsFiltered(filters)
    const groupedMenuData = this.groupMenuData(this.props, stations)

    let message = 'No items to show.'
    if (cafeMessage) {
      message = cafeMessage
    } else if (specialsFilterEnabled && stations.length === 0) {
      message =
        'No items to show. There may be no specials today. Try changing the filters.'
    } else if (anyFiltersEnabled && !size(groupedMenuData)) {
      message = 'No items to show. Try changing the filters.'
    }

    const messageView = <NoticeView style={styles.message} text={message} />

    const header = (
      <FilterToolbar
        date={now}
        title={mealName}
        filters={filters}
        onPress={this.openFilterView}
      />
    )

    return (
      <SectionList
        ItemSeparatorComponent={Separator}
        ListEmptyComponent={messageView}
        ListHeaderComponent={header}
        data={filters}
        keyExtractor={this.keyExtractor}
        onRefresh={this.props.onRefresh}
        refreshing={this.props.refreshing}
        renderItem={this.renderItem}
        renderSectionHeader={this.renderSectionHeader}
        sections={(groupedMenuData: any)}
        style={styles.inner}
      />
    )
  }
}

const mapState = (
  state: ReduxState,
  actualProps: ReactProps,
): ReduxStateProps => {
  if (!state.menus) {
    return {filters: []}
  }
  return {
    filters: state.menus[actualProps.name] || [],
  }
}

const mapDispatch = (dispatch, actualProps: ReactProps): ReduxDispatchProps => {
  return {
    onFiltersChange: (filters: FilterType[]) =>
      dispatch(updateMenuFilters(actualProps.name, filters)),
  }
}

export const ConnectedFancyMenu = connect(mapState, mapDispatch)(FancyMenu)
<|endoftext|>


<commit_before>// @flow
import React from 'react'
import {ScrollView, Text, WebView, StyleSheet} from 'react-native'
import moment from 'moment'

import {Cell} from 'react-native-tableview-simple'
import {Card} from '../components/card'
import * as c from '../components/colors'
import type {StudentOrgType} from './types'

const styles = StyleSheet.create({
  name: {
    textAlign: 'center',
    marginTop: 20,
    marginBottom: 15,
    paddingHorizontal: 5,
    color: c.black,
    fontSize: 32,
    fontWeight: '300',
  },
  card: {
    marginBottom: 20,
  },
  cardBody: {
    color: c.black,
    paddingTop: 13,
    paddingBottom: 13,
    paddingLeft: 16,
    paddingRight: 16,
    fontSize: 16,
  },
  description: {
    paddingTop: 13,
    paddingBottom: 13,
    paddingLeft: 16,
    paddingRight: 16,
    backgroundColor: c.white,
    height: 200,
  },
  footer: {
    fontSize: 10,
    color: c.iosDisabledText,
    textAlign: 'center',
  },
  lastUpdated: {
    paddingBottom: 10,
  },
  poweredBy: {
    paddingBottom: 20,
  },
})

export class StudentOrgsDetailRenderView extends React.Component {
  props: {
    org: StudentOrgType,
  }

  render() {
    const data = this.props.org
    const name = data.name.trim()
    let {
      category,
      meetings,
      contacts,
      description,
      advisors,
      lastUpdated,
      website,
    } = data

    advisors = advisors.filter(c => c.name.trim().length)

    return (
      <ScrollView>
        <Text style={styles.name}>{name}</Text>

        {category.trim()
          ? <Card header="Category" style={styles.card}>
              <Text style={styles.cardBody}>{category}</Text>
            </Card>
          : null}

        {meetings.trim()
          ? <Card header="Meetings" style={styles.card}>
              <Cell cellStyle="Basic" title={meetings} />
            </Card>
          : null}

        {website.trim()
          ? <Card header="Website" style={styles.card}>
              <Text style={styles.cardBody}>
                {/^https?:\/\//.test(website) ? website : `http://${website}`)
              </Text>
            </Card>
          : null}

        <Card header="Contact" style={styles.card}>
          {contacts.map((c, i) => (
            <Text key={i} selectable={true} style={styles.cardBody}>
              {c.title}: {c.firstName} {c.lastName} ({c.email})
            </Text>
          ))}
        </Card>

        {advisors.length
          ? <Card
              header={advisors.length === 1 ? 'ADVISOR' : 'ADVISORS'}
              style={styles.card}
            >
              {advisors.map((c, i) => (
                <Text key={i} selectable={true} style={styles.cardBody}>
                  {c.name} ({c.email})
                </Text>
              ))}
            </Card>
          : null}

        {description.trim()
          ? <Card header="Description" style={styles.card}>
              <WebView
                scrollEnabled={true}
                style={styles.description}
                source={{
                  html: `
              <style>
                body {
                  margin: 10px 15px 10px;
                  font-family: -apple-system;
                }
                * {
                  max-width: 100%;
                }
              </style>
              ${description.trim()}
            `,
                }}
              />
            </Card>
          : null}

        <Text selectable={true} style={[styles.footer, styles.lastUpdated]}>
          Last updated:
          {' '}
          {moment(lastUpdated, 'MMMM, DD YYYY HH:mm:ss').calendar()}
        </Text>

        <Text selectable={true} style={[styles.footer, styles.poweredBy]}>
          Powered by the St. Olaf Student Orgs Database
        </Text>
      </ScrollView>
    )
  }
}
<commit_msg>fix Advisor heading capitalization on android<commit_after>// @flow
import React from 'react'
import {ScrollView, Text, WebView, StyleSheet} from 'react-native'
import moment from 'moment'

import {Cell} from 'react-native-tableview-simple'
import {Card} from '../components/card'
import * as c from '../components/colors'
import type {StudentOrgType} from './types'

const styles = StyleSheet.create({
  name: {
    textAlign: 'center',
    marginTop: 20,
    marginBottom: 15,
    paddingHorizontal: 5,
    color: c.black,
    fontSize: 32,
    fontWeight: '300',
  },
  card: {
    marginBottom: 20,
  },
  cardBody: {
    color: c.black,
    paddingTop: 13,
    paddingBottom: 13,
    paddingLeft: 16,
    paddingRight: 16,
    fontSize: 16,
  },
  description: {
    paddingTop: 13,
    paddingBottom: 13,
    paddingLeft: 16,
    paddingRight: 16,
    backgroundColor: c.white,
    height: 200,
  },
  footer: {
    fontSize: 10,
    color: c.iosDisabledText,
    textAlign: 'center',
  },
  lastUpdated: {
    paddingBottom: 10,
  },
  poweredBy: {
    paddingBottom: 20,
  },
})

export class StudentOrgsDetailRenderView extends React.Component {
  props: {
    org: StudentOrgType,
  }

  render() {
    const data = this.props.org
    const name = data.name.trim()
    let {
      category,
      meetings,
      contacts,
      description,
      advisors,
      lastUpdated,
      website,
    } = data

    advisors = advisors.filter(c => c.name.trim().length)

    return (
      <ScrollView>
        <Text style={styles.name}>{name}</Text>

        {category.trim()
          ? <Card header="Category" style={styles.card}>
              <Text style={styles.cardBody}>{category}</Text>
            </Card>
          : null}

        {meetings.trim()
          ? <Card header="Meetings" style={styles.card}>
              <Cell cellStyle="Basic" title={meetings} />
            </Card>
          : null}

        {website.trim()
          ? <Card header="Website" style={styles.card}>
              <Text style={styles.cardBody}>
                {/^https?:\/\//.test(website) ? website : `http://${website}`)
              </Text>
            </Card>
          : null}

        <Card header="Contact" style={styles.card}>
          {contacts.map((c, i) => (
            <Text key={i} selectable={true} style={styles.cardBody}>
              {c.title}: {c.firstName} {c.lastName} ({c.email})
            </Text>
          ))}
        </Card>

        {advisors.length
          ? <Card
              header={advisors.length === 1 ? 'Advisor' : 'Advisors'}
              style={styles.card}
            >
              {advisors.map((c, i) => (
                <Text key={i} selectable={true} style={styles.cardBody}>
                  {c.name} ({c.email})
                </Text>
              ))}
            </Card>
          : null}

        {description.trim()
          ? <Card header="Description" style={styles.card}>
              <WebView
                scrollEnabled={true}
                style={styles.description}
                source={{
                  html: `
              <style>
                body {
                  margin: 10px 15px 10px;
                  font-family: -apple-system;
                }
                * {
                  max-width: 100%;
                }
              </style>
              ${description.trim()}
            `,
                }}
              />
            </Card>
          : null}

        <Text selectable={true} style={[styles.footer, styles.lastUpdated]}>
          Last updated:
          {' '}
          {moment(lastUpdated, 'MMMM, DD YYYY HH:mm:ss').calendar()}
        </Text>

        <Text selectable={true} style={[styles.footer, styles.poweredBy]}>
          Powered by the St. Olaf Student Orgs Database
        </Text>
      </ScrollView>
    )
  }
}
<|endoftext|>


<commit_before>// @flow
import React from 'react'
import {View, Text, StyleSheet, Platform} from 'react-native'
import type {BusLineType, FancyBusTimeListType, BusScheduleType} from './types'
import {getScheduleForNow, getSetOfStopsForNow} from './lib'
import get from 'lodash/get'
import zip from 'lodash/zip'
import head from 'lodash/head'
import isEqual from 'lodash/isEqual'
import last from 'lodash/last'
import moment from 'moment-timezone'
import * as c from '../../components/colors'
import {Separator} from '../../components/separator'
import {BusStopRow} from './bus-stop-row'
import {
  ListRow,
  ListSectionHeader,
  Title,
  ListFooter,
} from '../../components/list'

const TIME_FORMAT = 'h:mma'
const TIMEZONE = 'America/Winnipeg'

const styles = StyleSheet.create({
  separator: {
    marginLeft: 45,
    // erase the gap in the bar caused by the separators' block-ness
    marginTop: -1,
  },
})

const barColors = {
  'Blue Line': c.steelBlue,
  'Express Bus': c.moneyGreen,
  'Red Line': c.salmon,
}
const stopColors = {
  'Blue Line': c.midnightBlue,
  'Express Bus': c.hollyGreen,
  'Red Line': c.brickRed,
}

function makeSubtitle({now, moments, isLastBus}) {
  let lineDetail = 'Running'

  if (now.isBefore(head(moments))) {
    lineDetail = `Starts ${now.clone().seconds(0).to(head(moments))}`
  } else if (now.isAfter(last(moments))) {
    lineDetail = 'Over for Today'
  } else if (isLastBus) {
    lineDetail = 'Last Bus'
  }

  return lineDetail
}

const parseTime = (now: moment) => (time: string | false) => {
  // either pass `false` through or return a parsed time
  if (time === false) {
    return false
  }

  return (
    moment
      // interpret in Central time
      .tz(time, TIME_FORMAT, true, TIMEZONE)
      // and set the date to today
      .dayOfYear(now.dayOfYear())
  )
}

type Props = {
  line: BusLineType,
  now: moment,
  openMap: () => any,
}

type State = {
  schedule: ?BusScheduleType,
  scheduledMoments: Array<FancyBusTimeListType>,
  currentMoments: FancyBusTimeListType,
  stopTitleTimePairs: Array<[string, moment]>,
}

export class BusLine extends React.Component<void, Props, State> {
  state = {
    schedule: null,
    scheduledMoments: [],
    currentMoments: [],
    stopTitleTimePairs: [],
    firstUpdate: true,
  }

  componentWillMount() {
    this.setStateFromProps(this.props)
  }

  componentWillReceiveProps(nextProps: Props) {
    this.setStateFromProps(nextProps)
  }

  shouldComponentUpdate(nextProps: Props, nextState: State) {
    // We won't check the time in shouldComponentUpdate, because we really
    // only care if the bus information has changed, and this is called after
    // setStateFromProps runs.

    return (
      this.props.now.isSame(nextProps.now, 'minute') ||
      this.props.line !== nextProps.line ||
      this.props.openMap !== nextProps.openMap ||
      !isEqual(this.state.currentMoments, nextState.currentMoments)
    )
  }

  setStateFromProps = (nextProps: Props) => {
    if (
      this.props.now.isSame(nextProps.now, 'minute') &&
      !this.state.firstUpdate
    ) {
      return
    }

    const {line, now} = nextProps

    const schedule = getScheduleForNow(line.schedules, now)
    if (!schedule) {
      return
    }

    const parseTimes = timeset => timeset.map(parseTime(now))
    const scheduledMoments: Array<FancyBusTimeListType> = schedule.times.map(
      parseTimes,
    )

    const currentMoments: FancyBusTimeListType = getSetOfStopsForNow(
      scheduledMoments,
      now,
    )

    const stopTitleTimePairs: Array<[string, moment]> = zip(
      schedule.stops,
      currentMoments,
    )

    this.setState(() => ({
      schedule,
      scheduledMoments,
      currentMoments,
      stopTitleTimePairs,
      firstUpdate: false,
    }))
  }

  render() {
    const {line, now} = this.props
    const {
      schedule,
      scheduledMoments,
      currentMoments,
      stopTitleTimePairs,
    } = this.state

    // grab the colors (with fallbacks) via _.get
    const barColor = get(barColors, line.line, c.black)
    const currentStopColor = get(stopColors, line.line, c.gray)
    const androidColor = Platform.OS === 'android' ? {color: barColor} : null

    if (!schedule) {
      return (
        <View>
          <ListSectionHeader title={line.line} titleStyle={androidColor} />
          <ListRow>
            <Title><Text>This line is not running today.</Text></Title>
          </ListRow>
        </View>
      )
    }

    const timesIndex = scheduledMoments.indexOf(currentMoments)
    const isLastBus = timesIndex === scheduledMoments.length - 1
    const subtitle = makeSubtitle({now, moments: currentMoments, isLastBus})

    return (
      <View>
        <ListSectionHeader
          title={line.line}
          subtitle={subtitle}
          titleStyle={androidColor}
        />

        {stopTitleTimePairs.map(([placeTitle, moment], i, list) =>
          <View key={i}>
            <BusStopRow
              // get the arrival time for this stop from each bus loop after
              // the current time (as given by `now`)
              times={scheduledMoments.slice(timesIndex).map(set => set[i])}
              place={placeTitle}
              now={now}
              time={moment}
              barColor={barColor}
              currentStopColor={currentStopColor}
              isFirstRow={i === 0}
              isLastRow={i === list.length - 1}
            />
            {i < list.length - 1
              ? <Separator style={styles.separator} />
              : null}
          </View>,
        )}

        {/*<ListRow
          onPress={this.props.openMap}
          fullWidth={true}
          spacing={{left: 45}}
        >
          <Row alignItems="center">
            <Column alignItems="center" width={45} paddingRight={5}>
              <Icon
                name={
                  Platform.OS === 'ios' ? 'ios-navigate-outline' : 'md-navigate'
                }
                size={24}
                style={{color: c.iosDisabledText}}
              />
            </Column>

            <Column>
              <Title>
                <Text>Open Map</Text>
              </Title>

              <Detail>
                <Text>See the planned bus route on a map!</Text>
              </Detail>
            </Column>
          </Row>
        </ListRow>*/}

        <ListFooter
          title={
            'Bus routes and times subject to change without notice\n\nData collected by the humans of All About Olaf'
          }
        />
      </View>
    )
  }
}
<commit_msg>don't compare any state items in sCU<commit_after>// @flow
import React from 'react'
import {View, Text, StyleSheet, Platform} from 'react-native'
import type {BusLineType, FancyBusTimeListType, BusScheduleType} from './types'
import {getScheduleForNow, getSetOfStopsForNow} from './lib'
import get from 'lodash/get'
import zip from 'lodash/zip'
import head from 'lodash/head'
import last from 'lodash/last'
import moment from 'moment-timezone'
import * as c from '../../components/colors'
import {Separator} from '../../components/separator'
import {BusStopRow} from './bus-stop-row'
import {
  ListRow,
  ListSectionHeader,
  Title,
  ListFooter,
} from '../../components/list'

const TIME_FORMAT = 'h:mma'
const TIMEZONE = 'America/Winnipeg'

const styles = StyleSheet.create({
  separator: {
    marginLeft: 45,
    // erase the gap in the bar caused by the separators' block-ness
    marginTop: -1,
  },
})

const barColors = {
  'Blue Line': c.steelBlue,
  'Express Bus': c.moneyGreen,
  'Red Line': c.salmon,
}
const stopColors = {
  'Blue Line': c.midnightBlue,
  'Express Bus': c.hollyGreen,
  'Red Line': c.brickRed,
}

function makeSubtitle({now, moments, isLastBus}) {
  let lineDetail = 'Running'

  if (now.isBefore(head(moments))) {
    lineDetail = `Starts ${now.clone().seconds(0).to(head(moments))}`
  } else if (now.isAfter(last(moments))) {
    lineDetail = 'Over for Today'
  } else if (isLastBus) {
    lineDetail = 'Last Bus'
  }

  return lineDetail
}

const parseTime = (now: moment) => (time: string | false) => {
  // either pass `false` through or return a parsed time
  if (time === false) {
    return false
  }

  return (
    moment
      // interpret in Central time
      .tz(time, TIME_FORMAT, true, TIMEZONE)
      // and set the date to today
      .dayOfYear(now.dayOfYear())
  )
}

type Props = {
  line: BusLineType,
  now: moment,
  openMap: () => any,
}

type State = {
  schedule: ?BusScheduleType,
  scheduledMoments: Array<FancyBusTimeListType>,
  currentMoments: FancyBusTimeListType,
  stopTitleTimePairs: Array<[string, moment]>,
}

export class BusLine extends React.Component<void, Props, State> {
  state = {
    schedule: null,
    scheduledMoments: [],
    currentMoments: [],
    stopTitleTimePairs: [],
    firstUpdate: true,
  }

  componentWillMount() {
    this.setStateFromProps(this.props)
  }

  componentWillReceiveProps(nextProps: Props) {
    this.setStateFromProps(nextProps)
  }

  shouldComponentUpdate(nextProps: Props, nextState: State) {
    // We won't check the time in shouldComponentUpdate, because we really
    // only care if the bus information has changed, and this is called after
    // setStateFromProps runs.

    return (
      this.props.now.isSame(nextProps.now, 'minute') ||
      this.props.line !== nextProps.line ||
      this.props.openMap !== nextProps.openMap
    )
  }

  setStateFromProps = (nextProps: Props) => {
    if (
      this.props.now.isSame(nextProps.now, 'minute') &&
      !this.state.firstUpdate
    ) {
      return
    }

    const {line, now} = nextProps

    const schedule = getScheduleForNow(line.schedules, now)
    if (!schedule) {
      return
    }

    const parseTimes = timeset => timeset.map(parseTime(now))
    const scheduledMoments: Array<FancyBusTimeListType> = schedule.times.map(
      parseTimes,
    )

    const currentMoments: FancyBusTimeListType = getSetOfStopsForNow(
      scheduledMoments,
      now,
    )

    const stopTitleTimePairs: Array<[string, moment]> = zip(
      schedule.stops,
      currentMoments,
    )

    this.setState(() => ({
      schedule,
      scheduledMoments,
      currentMoments,
      stopTitleTimePairs,
      firstUpdate: false,
    }))
  }

  render() {
    const {line, now} = this.props
    const {
      schedule,
      scheduledMoments,
      currentMoments,
      stopTitleTimePairs,
    } = this.state

    // grab the colors (with fallbacks) via _.get
    const barColor = get(barColors, line.line, c.black)
    const currentStopColor = get(stopColors, line.line, c.gray)
    const androidColor = Platform.OS === 'android' ? {color: barColor} : null

    if (!schedule) {
      return (
        <View>
          <ListSectionHeader title={line.line} titleStyle={androidColor} />
          <ListRow>
            <Title><Text>This line is not running today.</Text></Title>
          </ListRow>
        </View>
      )
    }

    const timesIndex = scheduledMoments.indexOf(currentMoments)
    const isLastBus = timesIndex === scheduledMoments.length - 1
    const subtitle = makeSubtitle({now, moments: currentMoments, isLastBus})

    return (
      <View>
        <ListSectionHeader
          title={line.line}
          subtitle={subtitle}
          titleStyle={androidColor}
        />

        {stopTitleTimePairs.map(([placeTitle, moment], i, list) =>
          <View key={i}>
            <BusStopRow
              // get the arrival time for this stop from each bus loop after
              // the current time (as given by `now`)
              times={scheduledMoments.slice(timesIndex).map(set => set[i])}
              place={placeTitle}
              now={now}
              time={moment}
              barColor={barColor}
              currentStopColor={currentStopColor}
              isFirstRow={i === 0}
              isLastRow={i === list.length - 1}
            />
            {i < list.length - 1
              ? <Separator style={styles.separator} />
              : null}
          </View>,
        )}

        {/*<ListRow
          onPress={this.props.openMap}
          fullWidth={true}
          spacing={{left: 45}}
        >
          <Row alignItems="center">
            <Column alignItems="center" width={45} paddingRight={5}>
              <Icon
                name={
                  Platform.OS === 'ios' ? 'ios-navigate-outline' : 'md-navigate'
                }
                size={24}
                style={{color: c.iosDisabledText}}
              />
            </Column>

            <Column>
              <Title>
                <Text>Open Map</Text>
              </Title>

              <Detail>
                <Text>See the planned bus route on a map!</Text>
              </Detail>
            </Column>
          </Row>
        </ListRow>*/}

        <ListFooter
          title={
            'Bus routes and times subject to change without notice\n\nData collected by the humans of All About Olaf'
          }
        />
      </View>
    )
  }
}
<|endoftext|>


<commit_before>/*
 * app/src/Buttons.js
 *
 *
 * Copyright (C) 2015  Pierre Marchand <pierremarc07@gmail.com>
 *
 * License in LICENSE file at the root of the repository.
 *
 */

var semaphore = require('../lib/Semaphore');

function loginButton(console, button) {
    var isLogged = false;
    button.setAttribute('class','wc-button icon-login');
    button.innerHTML = 'Login';

    button.addEventListener('click', function(){
        if(!isLogged) {
            console.runCommand('login');
        }
        else {
            console.runCommand('logout');
        }
    });

    semaphore.on('user:login', function (user) {
        isLogged = true;
        button.innerHTML = 'Logout';
    });
    semaphore.on('user:logout', function () {
        isLogged = false;
        button.innerHTML = 'Login';
    });
}

 var shellButtons = {
     'Help' : {
         type: 'display',
         command: ['help']
     },
     'Login' : {
         type: 'function',
         command: loginButton
     },
     'About' : {
         type: 'shell',
         command: ['cc /2232525b-a8cb-4579-af24-2b5629ba43b5/3a7f695b-cd37-43f4-9a7a-efb1e422aef8', 'view'],
     },
 };

 var userButtons = {
     'List maps': {
        type: 'embed',
        command: ['lg']
     },
     'Add map': {
        type: 'display',
        command: ['mkgroup']
    },
     'Upload image': {
         type: 'display',
         command: ['media upload']
     },
     'Browse images': {
         type: 'display',
         command: ['media pick']
     },
     'My profile' : {
         type: 'shell',
         command: ['cc /me', 'get']
     }

 };

 var groupButtons = {
     'List layers' : {
         type: 'embed',
         command: ['ll']
     },
     'Add layer': {
         type: 'display',
         command: ['mklayer']
     },
     'Show - Hide layers': {
         type: 'display',
         command: ['visible | set visible']
     },
     'Re-order layers': {
         type: 'display',
         command: ['visible | edit | set visible']
     },
     'Set map extent': {
         type: 'shell',
         command: ['region get | set extent']
     },
     'Select Features': {
         type: 'display',
         command: ['select']
     }
 };

 var layerButtons = {
    'List features': {
        type: 'embed',
        command: ['lf']
    },
     'Trace': {
         type: 'display',
         command: ['trace | create']
     },
     'Draw line': {
         type: 'display',
         command: ['draw | create']
     },
     'Draw zone': {
         type: 'display',
         command: ['draw | close | create']
     },
     'Import geo-datas': {
         type: 'display',
         command: ['import','lf']
     }
 };

 var featureButtons = {
     'Set name': {
         type: 'display',
         command: ['get name | edit | set name']
     },
     'Set image': {
         type: 'display',
         command: ['media pick | set params.image']
     },
     'Set color': {
         type: 'display',
         command: ['get style.strokeStyle | edit | set style.strokeStyle']
     },
     'Set text': {
         type: 'display',
         command: ['get params.text | edit | set params.text']
     },
     'Edit geometry' : {
         type: 'display',
         command: ['gg | trace | sg']
     },
     'Zoom to feature' : {
         type: 'shell',
         command: ['gg | region set']
     },
     'Delete feature' : {
         type: 'shell',
         command: ['del_feature', 'lf']
     }

 };

module.exports = exports = {
    'shell' : shellButtons,
    'user' : userButtons,
    'group': groupButtons,
    'layer': layerButtons,
    'feature': featureButtons,
};
<commit_msg>fixed visible buttons<commit_after>/*
 * app/src/Buttons.js
 *
 *
 * Copyright (C) 2015  Pierre Marchand <pierremarc07@gmail.com>
 *
 * License in LICENSE file at the root of the repository.
 *
 */

var semaphore = require('../lib/Semaphore');

function loginButton(console, button) {
    var isLogged = false;
    button.setAttribute('class','wc-button icon-login');
    button.innerHTML = 'Login';

    button.addEventListener('click', function(){
        if(!isLogged) {
            console.runCommand('login');
        }
        else {
            console.runCommand('logout');
        }
    });

    semaphore.on('user:login', function (user) {
        isLogged = true;
        button.innerHTML = 'Logout';
    });
    semaphore.on('user:logout', function () {
        isLogged = false;
        button.innerHTML = 'Login';
    });
}

 var shellButtons = {
     'Help' : {
         type: 'display',
         command: ['help']
     },
     'Login' : {
         type: 'function',
         command: loginButton
     },
     'About' : {
         type: 'shell',
         command: ['cc /2232525b-a8cb-4579-af24-2b5629ba43b5/3a7f695b-cd37-43f4-9a7a-efb1e422aef8', 'view'],
     },
 };

 var userButtons = {
     'List maps': {
        type: 'embed',
        command: ['lg']
     },
     'Add map': {
        type: 'display',
        command: ['mkgroup']
    },
     'Upload image': {
         type: 'display',
         command: ['media upload']
     },
     'Browse images': {
         type: 'display',
         command: ['media pick']
     },
     'My profile' : {
         type: 'shell',
         command: ['cc /me', 'get']
     }

 };

 var groupButtons = {
     'List layers' : {
         type: 'embed',
         command: ['ll']
     },
     'Add layer': {
         type: 'display',
         command: ['mklayer']
     },
     'Show - Hide layers': {
         type: 'display',
         command: ['visible']
     },
     // 'Re-order layers': {
     //     type: 'display',
     //     command: ['visible | edit | set visible']
     // },
     'Set map extent': {
         type: 'shell',
         command: ['region get | set extent']
     },
     'Select Features': {
         type: 'display',
         command: ['select']
     }
 };

 var layerButtons = {
    'List features': {
        type: 'embed',
        command: ['lf']
    },
     'Trace': {
         type: 'display',
         command: ['trace | create']
     },
     'Draw line': {
         type: 'display',
         command: ['draw | create']
     },
     'Draw zone': {
         type: 'display',
         command: ['draw | close | create']
     },
     'Import geo-datas': {
         type: 'display',
         command: ['import','lf']
     }
 };

 var featureButtons = {
     'Set name': {
         type: 'display',
         command: ['get name | edit | set name']
     },
     'Set image': {
         type: 'display',
         command: ['media pick | set params.image']
     },
     'Set color': {
         type: 'display',
         command: ['get style.strokeStyle | edit | set style.strokeStyle']
     },
     'Set text': {
         type: 'display',
         command: ['get params.text | edit | set params.text']
     },
     'Edit geometry' : {
         type: 'display',
         command: ['gg | trace | sg']
     },
     'Zoom to feature' : {
         type: 'shell',
         command: ['gg | region set']
     },
     'Delete feature' : {
         type: 'shell',
         command: ['del_feature', 'lf']
     }

 };

module.exports = exports = {
    'shell' : shellButtons,
    'user' : userButtons,
    'group': groupButtons,
    'layer': layerButtons,
    'feature': featureButtons,
};
<|endoftext|>


<commit_before>var init = {
    /*
     * Notice when (auto)depublish date is in the past
     * TODO: add timer, to check depublish date has passed during editing.
     *
     * @returns {undefined}
     */
    depublishTracking: function () {
        var noticeID = 'dateDepublishNotice',
            msg = $('#datedepublish').data('notice');

        $('#datedepublish, #statusselect').on('change', function(event){

            var status = $('#statusselect').val(),
                depublish = $('#datedepublish').val();

            // remove old notice
            $('.'+noticeID).remove();

            if (depublish === '') {
                return;
            }

            if (status === 'published' && moment(depublish + Bolt.conf('timezone.offset')) < moment()) {
                $('
' +
                    '<button type="button" class="close" data-dismiss="alert">×</button>' + msg + '
')
                    .hide()
                    .insertAfter('.depublish-group')
                    .slideDown('fast');
            }

        });

        // trigger on load
        $('#datedepublish').trigger('change');

    },

    /*
     * Bind editfile field
     *
     * @param {object} data
     * @returns {undefined}
     */
    bindEditFile: function (data) {
        $('#saveeditfile').bind('click', function (e) {

            // If not on mobile (i.e. Codemirror is present), copy back to the textarea.
            if (typeof(CodeMirror) !== 'undefined') {
                $('#form_contents').val(editor.getValue());
            }

            // Ping @rarila: How the heck would I get bolt.data('editcontent.msg.saving') here?
            var saving = "Saving …",
                savedon = $('p.lastsaved').html(),
                msgNotSaved = "Not saved";

            // Disable the buttons, to indicate stuff is being done.
            $('#saveeditfile').addClass('disabled');
            $('#saveeditfile i').addClass('fa-spin fa-spinner');
            $('p.lastsaved').text(saving);

            $.post('?returnto=ajax', $('#editfile').serialize())
                .done(function (data) {
                    if (!data.ok) {
                        alert(data.msg);
                    }
                    $('p.lastsaved').html(data.msg);
                })
                .fail(function(){
                    alert(msgNotSaved);
                })
                .always(function(){
                    // Re-enable buttons
                    window.setTimeout(function(){
                        $('#saveeditfile').removeClass('disabled').blur();
                        $('#saveeditfile i').removeClass('fa-spin fa-spinner');
                    }, 300);
                });
        });

        if (typeof(CodeMirror) !== 'undefined') {
            var editor = CodeMirror.fromTextArea(document.getElementById('form_contents'), {
                lineNumbers: true,
                autofocus: true,
                tabSize: 4,
                indentUnit: 4,
                indentWithTabs: false,
                readOnly: data.readonly
            });
            var newheight = $(window).height() - 312;
            if (newheight < 200) {
                newheight = 200;
            }
            editor.setSize(null, newheight);
        }

    },

    /*
     * Bind editlocale field
     *
     * @param {object} data
     * @returns {undefined}
     */
    bindEditLocale: function (data) {
        var editor = CodeMirror.fromTextArea(document.getElementById('form_contents'), {
            lineNumbers: true,
            autofocus: true,
            tabSize: 4,
            indentUnit: 4,
            indentWithTabs: false,
            readOnly: data.readonly
        });

        editor.setSize(null, $(window).height() - 276);
    },

    /*
     * Bind filebrowser
     */
    bindFileBrowser: function () {
        $('#myTab a').click(function (e) {
            e.preventDefault();
            $(this).tab('show');
        });

        var getUrlParam = function(paramName) {
            var reParam = new RegExp('(?:[\?&]|&)' + paramName + '=([^&]+)', 'i'),
                match = window.location.search.match(reParam);

            return (match && match.length > 1) ? match[1] : null;
        };
        var funcNum = getUrlParam('CKEditorFuncNum');

        $('a.filebrowserCallbackLink').bind('click', function (e) {
            e.preventDefault();
            var url = $(this).attr('href');
            window.opener.CKEDITOR.tools.callFunction(funcNum, url);
            window.close();
        });

        $('a.filebrowserCloseLink').bind('click', function () {
            window.close();
        });
    },

    bindCkFileSelect: function (data) {
        var getUrlParam = function (paramName) {
            var reParam = new RegExp('(?:[\?&]|&)' + paramName + '=([^&]+)', 'i'),
                match = window.location.search.match(reParam);

            return (match && match.length > 1) ? match[1] : null;
        };

        var funcNum = getUrlParam('CKEditorFuncNum');
        $('a.filebrowserCallbackLink').bind('click', function (event) {
            event.preventDefault();
            var url = $(this).attr('href');
            window.opener.CKEDITOR.tools.callFunction(funcNum, url);
            window.close();
        });
    },

    /*
     * Bind prefill
     */
    bindPrefill: function () {
        $('#check-all').on('click', function() {
            // because jQuery is being retarded.
            // See: http://stackoverflow.com/questions/5907645/jquery-chrome-and-checkboxes-strange-behavior
            $("#form_contenttypes :checkbox").removeAttr('checked').trigger('click');
        });
        $('#uncheck-all').on('click', function() {
            $("#form_contenttypes :checkbox").removeAttr('checked');
        });
    },

    /**
     * Any link (or clickable -icon) with a class='confirm' gets a confirmation dialog.
     *
     * @returns {undefined}
     */
    confirmationDialogs: function () {
        $('.confirm').on('click', function () {
            return confirm($(this).data('confirm'));
        });
    },

    /*
     * Dashboard listing checkboxes
     *
     * @returns {undefined}
     */
    dashboardCheckboxes: function () {
        var getSelectedItems = function () {
            var aItems = [];

            $('.dashboardlisting input:checked').each(function () {
                if ($(this).parents('tr').attr('id')) {
                    aItems.push($(this).parents('tr').attr('id').substr(5));
                }
            });

            return aItems;
        };

        // Check all checkboxes
        $(".dashboardlisting tr th:first-child input:checkbox").click(function () {
            var checkedStatus = this.checked;
            $(this).closest('tbody').find('td input:checkbox').each(function () {
                this.checked = checkedStatus;
                if (checkedStatus === this.checked) {
                    $(this).closest('table tbody tr').removeClass('row-checked');
                }
                if (this.checked) {
                    $(this).closest('table tbody tr').addClass('row-checked');
                }
            });
        });
        // Check if any records in the overview have been checked, and if so: show action buttons
        $('.dashboardlisting input:checkbox').click(function () {
            var aItems = getSelectedItems();
            if (aItems.length >= 1) {
                // if checked
                $('a.checkchosen').removeClass('disabled');
                $('a.showifchosen').show();
            } else {
                // if none checked
                $('a.checkchosen').addClass('disabled');
                $('a.showifchosen').hide();
            }
        });
        // Delete chosen Items
        $("a.deletechosen").click(function (e) {
            e.preventDefault();
            var aItems = getSelectedItems(),
                notice,
                rec;

            if (aItems.length > 0) {
                notice = aItems.length === 1 ?
                    Bolt.data('recordlisting.delete_one') : Bolt.data('recordlisting.delete_mult');
                bootbox.confirm(notice, function (confirmed) {
                    $('.alert').alert();
                    if (confirmed === true) {
                        // Delete request
                        $.ajax({
                            url: Bolt.conf('paths.bolt') + 'content/deletecontent/' +
                                $('#item_' + aItems[0]).closest('table').data('contenttype') + '/' + aItems.join(',') +
                                '?bolt_csrf_token=' + $('#item_' + aItems[0]).closest('table').data('bolt_csrf_token'),
                            type: 'get',
                            success: function (feedback) {
                                var items = [];
                                $.each(aItems, function (index, id) {
                                    items.push(document.getElementById('item_' + id));
                                });

                                $(items).hide();
                                $('a.deletechosen').hide();
                            }
                        });
                    }
                });
            }
        });
    },

    /*
     * Render any deferred widgets, if any.
     *
     * @returns {undefined}
     */
    deferredWidgets: function () {
        $('div.widget').each(function () {
            if (typeof $(this).data('defer') === 'undefined') {
                return;
            }

            var key = $(this).data('key');

            $.ajax({
                url: Bolt.conf('paths.async') + 'widget/' + key,
                type: 'GET',
                success: function (result) {
                    $('#widget-' + key).html(result);
                },
                error: function () {
                    console.log('failed to get widget');
                }
            });
        });
    },

    /*
     * Smarter dropdowns/dropups based on viewport height.
     * Based on: https://github.com/twbs/bootstrap/issues/3637#issuecomment-9850709
     *
     * @returns {undefined}
     */
    dropDowns: function () {
        $('[data-toggle="dropdown"]').each(function (index, item) {
            var mouseEvt;
            if (typeof event === 'undefined') {
                $(item).parent().click(function (e) {
                    mouseEvt = e;
                });
            } else {
                mouseEvt = event;
            }
            $(item).parent().on('show.bs.dropdown', function (e) {

                // Prevent breakage on old IE.
                if (typeof mouseEvt !== "undefined" && mouseEvt !== null) {
                    var self = $(this).find('[data-toggle="dropdown"]'),
                    menu = self.next('.dropdown-menu'),
                    mousey = mouseEvt.pageY + 20,
                    menuHeight = menu.height(),
                    menuVisY = $(window).height() - mousey + menuHeight, // Distance from the bottom of viewport
                    profilerHeight = 37; // The size of the Symfony Profiler Bar is 37px.

                    // The whole menu must fit when trying to 'dropup', but always prefer to 'dropdown' (= default).
                    if ((mousey - menuHeight) > 20 && menuVisY < profilerHeight) {
                        menu.css({
                            top: 'auto',
                            bottom: '100%'
                        });
                    }
                }

            });
        });
    },

    /*
     * Show 'dropzone' for jQuery file uploader.
     *
     * @returns {undefined}
     */
    dropZone: function () {
        // @todo make it prettier, and distinguish between '.in' and '.hover'.
        $(document).bind('dragover', function (e) {
            var dropZone = $('.dropzone'),
                timeout = window.dropZoneTimeout;
            if (!timeout) {
                dropZone.addClass('in');
            } else {
                clearTimeout(timeout);
            }
            if (e.target === dropZone[0]) {
                dropZone.addClass('hover');
            } else {
                dropZone.removeClass('hover');
            }
            window.dropZoneTimeout = setTimeout(function () {
                window.dropZoneTimeout = null;
                dropZone.removeClass('in hover');
            }, 100);
        });
    },

    /*
     * Initialize the Magnific popup shizzle. Fancybox is still here as a trigger, for backwards compatibility.
     */
    magnificPopup: function () {
        //
        $('.magnific, .fancybox').magnificPopup({
            type: 'image',
            gallery: {
                enabled: true
            },
            disableOn: 400,
            closeBtnInside: true,
            enableEscapeKey: true,
            mainClass: 'mfp-with-zoom',
            zoom: {
                enabled: true,
                duration: 300,
                easing: 'ease-in-out',
                opener: function (openerElement) {
                    return openerElement.parent().parent().find('img');
                }
            }
        });
    },

    /*
     * Initialize current status display setting focus on status select
     *
     * @returns {undefined}
     */
    focusStatusSelect: function () {
        $('#lastsavedstatus').click(function (e) {
            e.preventDefault();
            $('a[href="#tab-meta"]').click();
            $('#statusselect').focus();
        });
     },

    /*
     * Omnisearch
     *
     * @returns {undefined}
     */
    omnisearch: function () {
        $('.omnisearch').select2({
            placeholder: '',
            minimumInputLength: 3,
            multiple: true, // this is for better styling …
            ajax: {
                url: Bolt.conf('paths.async') + 'omnisearch',
                dataType: 'json',
                data: function (term, page) {
                    return {
                        q: term
                    };
                },
                results: function (data, page) {
                    var results = [];
                    $.each(data, function (index, item) {
                        results.push({
                            id: item.path,
                            path: item.path,
                            label: item.label,
                            priority: item.priority
                        });
                    });

                    return {results: results};
                }
            },
            formatResult: function (item) {
                var markup = '

' +
                    '' +
                    '
' + item.label + '
' +
                    '
' + item.path + '
' +
                    '

';

                return markup;
            },
            formatSelection: function (item) {
                window.location.href = item.path;

                return item.label;
            },
            dropdownCssClass: "bigdrop",
            escapeMarkup: function (m) {
                return m;
            }
        });
    },

    /*
     * Toggle options for showing / hiding the password input on the logon screen.
     *
     * @returns {undefined}
     */
    passwordInput: function () {
        $(".togglepass").on('click', function () {
            if ($(this).hasClass('show-password')) {
                $('input[name="password"]').attr('type', 'text');
                $('.togglepass.show-password').hide();
                $('.togglepass.hide-password').show();
            } else {
                $('input[name="password"]').attr('type', 'password');
                $('.togglepass.show-password').show();
                $('.togglepass.hide-password').hide();
            }
        });

        $('.login-forgot').bind('click', function (e) {
            $('.login-group, .password-group').hide();
            $('.reset-group').show();
        });

        $('.login-remembered').bind('click', function (e) {
            $('.login-group, .password-group').show();
            $('.reset-group').hide();
        });
    },

    /*
     * Initialize popovers.
     */
    popOvers: function () {
        $('.info-pop').popover({
            trigger: 'hover',
            delay: {
                show: 500,
                hide: 200
            }
        });
    },

    uploads: function () {
        $('input[data-upload]').each(function (item) {
            var data = $(this).data('upload'),
                accept = $(this).attr('accept').replace(/\./g, ''),
                autocomplete_conf;

            switch (data.type) {
                case 'Image':
                case 'File':
                    bindFileUpload(data.key);

                    autocomplete_conf = {
                        source: Bolt.conf('paths.async') + 'file/autocomplete?ext=' + encodeURIComponent(accept),
                        minLength: 2
                    };
                    if (data.type === 'Image') {
                        autocomplete_conf.close = function () {
                            var path = $('#field-' + data.key).val(),
                                url;

                            if (path) {
                                url = Bolt.conf('paths.root') +'thumbs/' + data.width + 'x' + data.height + 'c/' +
                                      encodeURI(path);
                            } else {
                                url = Bolt.conf('paths.app') + 'view/img/default_empty_4x3.png';
                            }
                            $('#thumbnail-' + data.key).html(
                                ''
                            );
                        };
                    }
                    $('#field-' + data.key).autocomplete(autocomplete_conf);
                    break;

                case 'ImageList':
                    Bolt.imagelist[data.key] = new FilelistHolder({id: data.key, type: data.type});
                    break;

                case 'FileList':
                    Bolt.filelist[data.key] = new FilelistHolder({id: data.key, type: data.type});
                    break;
            }
        });
    },

    /*
     * ?
     */
    sortables: function () {
        $('tbody.sortable').sortable({
            items: 'tr',
            opacity: '0.5',
            axis: 'y',
            handle: '.sorthandle',
            update: function (e, ui) {
                var serial = $(this).sortable('serialize');
                // Sorting request
                $.ajax({
                    url: $('#baseurl').attr('value') + 'content/sortcontent/' +
                        $(this).parent('table').data('contenttype'),
                    type: 'POST',
                    data: serial,
                    success: function (feedback) {
                        // Do nothing
                    }
                });
            }
        });
    }
};<commit_msg>Some formatting<commit_after>var init = {
    /*
     * Notice when (auto)depublish date is in the past
     * TODO: add timer, to check depublish date has passed during editing.
     *
     * @returns {undefined}
     */
    depublishTracking: function () {
        var noticeID = 'dateDepublishNotice',
            msg = $('#datedepublish').data('notice');

        $('#datedepublish, #statusselect').on('change', function(event){

            var status = $('#statusselect').val(),
                depublish = $('#datedepublish').val();

            // remove old notice
            $('.'+noticeID).remove();

            if (depublish === '') {
                return;
            }

            if (status === 'published' && moment(depublish + Bolt.conf('timezone.offset')) < moment()) {
                $('
' +
                    '<button type="button" class="close" data-dismiss="alert">×</button>' + msg + '
')
                    .hide()
                    .insertAfter('.depublish-group')
                    .slideDown('fast');
            }

        });

        // trigger on load
        $('#datedepublish').trigger('change');

    },

    /*
     * Bind editfile field
     *
     * @param {object} data
     * @returns {undefined}
     */
    bindEditFile: function (data) {
        $('#saveeditfile').bind('click', function (e) {

            // If not on mobile (i.e. Codemirror is present), copy back to the textarea.
            if (typeof(CodeMirror) !== 'undefined') {
                $('#form_contents').val(editor.getValue());
            }

            // Ping @rarila: How the heck would I get bolt.data('editcontent.msg.saving') here?
            var saving = "Saving …",
                savedon = $('p.lastsaved').html(),
                msgNotSaved = "Not saved";

            // Disable the buttons, to indicate stuff is being done.
            $('#saveeditfile').addClass('disabled');
            $('#saveeditfile i').addClass('fa-spin fa-spinner');
            $('p.lastsaved').text(saving);

            $.post('?returnto=ajax', $('#editfile').serialize())
                .done(function (data) {
                    if (!data.ok) {
                        alert(data.msg);
                    }
                    $('p.lastsaved').html(data.msg);
                })
                .fail(function(){
                    alert(msgNotSaved);
                })
                .always(function(){
                    // Re-enable buttons
                    window.setTimeout(function(){
                        $('#saveeditfile').removeClass('disabled').blur();
                        $('#saveeditfile i').removeClass('fa-spin fa-spinner');
                    }, 300);
                });
        });

        if (typeof(CodeMirror) !== 'undefined') {
            var editor = CodeMirror.fromTextArea(document.getElementById('form_contents'), {
                lineNumbers: true,
                autofocus: true,
                tabSize: 4,
                indentUnit: 4,
                indentWithTabs: false,
                readOnly: data.readonly
            });
            var newheight = $(window).height() - 312;
            if (newheight < 200) {
                newheight = 200;
            }
            editor.setSize(null, newheight);
        }

    },

    /*
     * Bind editlocale field
     *
     * @param {object} data
     * @returns {undefined}
     */
    bindEditLocale: function (data) {
        var editor = CodeMirror.fromTextArea(document.getElementById('form_contents'), {
            lineNumbers: true,
            autofocus: true,
            tabSize: 4,
            indentUnit: 4,
            indentWithTabs: false,
            readOnly: data.readonly
        });

        editor.setSize(null, $(window).height() - 276);
    },

    /*
     * Bind filebrowser
     */
    bindFileBrowser: function () {
        $('#myTab a').click(function (e) {
            e.preventDefault();
            $(this).tab('show');
        });

        var getUrlParam = function(paramName) {
            var reParam = new RegExp('(?:[\?&]|&)' + paramName + '=([^&]+)', 'i'),
                match = window.location.search.match(reParam);

            return (match && match.length > 1) ? match[1] : null;
        };
        var funcNum = getUrlParam('CKEditorFuncNum');

        $('a.filebrowserCallbackLink').bind('click', function (e) {
            e.preventDefault();
            var url = $(this).attr('href');
            window.opener.CKEDITOR.tools.callFunction(funcNum, url);
            window.close();
        });

        $('a.filebrowserCloseLink').bind('click', function () {
            window.close();
        });
    },

    bindCkFileSelect: function (data) {
        var getUrlParam = function (paramName) {
            var reParam = new RegExp('(?:[\?&]|&)' + paramName + '=([^&]+)', 'i'),
                match = window.location.search.match(reParam);

            return (match && match.length > 1) ? match[1] : null;
        };

        var funcNum = getUrlParam('CKEditorFuncNum');
        $('a.filebrowserCallbackLink').bind('click', function (event) {
            event.preventDefault();
            var url = $(this).attr('href');
            window.opener.CKEDITOR.tools.callFunction(funcNum, url);
            window.close();
        });
    },

    /*
     * Bind prefill
     */
    bindPrefill: function () {
        $('#check-all').on('click', function() {
            // because jQuery is being retarded.
            // See: http://stackoverflow.com/questions/5907645/jquery-chrome-and-checkboxes-strange-behavior
            $("#form_contenttypes :checkbox").removeAttr('checked').trigger('click');
        });
        $('#uncheck-all').on('click', function() {
            $("#form_contenttypes :checkbox").removeAttr('checked');
        });
    },

    /**
     * Any link (or clickable -icon) with a class='confirm' gets a confirmation dialog.
     *
     * @returns {undefined}
     */
    confirmationDialogs: function () {
        $('.confirm').on('click', function () {
            return confirm($(this).data('confirm'));
        });
    },

    /*
     * Dashboard listing checkboxes
     *
     * @returns {undefined}
     */
    dashboardCheckboxes: function () {
        var getSelectedItems = function () {
            var aItems = [];

            $('.dashboardlisting input:checked').each(function () {
                if ($(this).parents('tr').attr('id')) {
                    aItems.push($(this).parents('tr').attr('id').substr(5));
                }
            });

            return aItems;
        };

        // Check all checkboxes.
        $('.dashboardlisting tr th:first-child input:checkbox').click(function () {
            var checkedStatus = this.checked;

            $(this).closest('tbody').find('td input:checkbox').each(function () {
                this.checked = checkedStatus;

                if (checkedStatus === this.checked) {
                    $(this).closest('table tbody tr').removeClass('row-checked');
                }
                if (this.checked) {
                    $(this).closest('table tbody tr').addClass('row-checked');
                }
            });
        });

        // Check if any records in the overview have been checked, and if so: show action buttons.
        $('.dashboardlisting input:checkbox').click(function () {
            var aItems = getSelectedItems();

            if (aItems.length >= 1) {
                // if checked
                $('a.checkchosen').removeClass('disabled');
                $('a.showifchosen').show();
            } else {
                // if none checked
                $('a.checkchosen').addClass('disabled');
                $('a.showifchosen').hide();
            }
        });

        // Delete chosen Items.
        $("a.deletechosen").click(function (e) {
            e.preventDefault();
            var aItems = getSelectedItems(),
                notice,
                rec;

            if (aItems.length > 0) {
                notice = aItems.length === 1 ?
                    Bolt.data('recordlisting.delete_one') : Bolt.data('recordlisting.delete_mult');
                bootbox.confirm(notice, function (confirmed) {
                    $('.alert').alert();
                    if (confirmed === true) {
                        // Delete request
                        $.ajax({
                            url: Bolt.conf('paths.bolt') + 'content/deletecontent/' +
                                $('#item_' + aItems[0]).closest('table').data('contenttype') + '/' + aItems.join(',') +
                                '?bolt_csrf_token=' + $('#item_' + aItems[0]).closest('table').data('bolt_csrf_token'),
                            type: 'get',
                            success: function (feedback) {
                                var items = [];
                                $.each(aItems, function (index, id) {
                                    items.push(document.getElementById('item_' + id));
                                });

                                $(items).hide();
                                $('a.deletechosen').hide();
                            }
                        });
                    }
                });
            }
        });
    },

    /*
     * Render any deferred widgets, if any.
     *
     * @returns {undefined}
     */
    deferredWidgets: function () {
        $('div.widget').each(function () {
            if (typeof $(this).data('defer') === 'undefined') {
                return;
            }

            var key = $(this).data('key');

            $.ajax({
                url: Bolt.conf('paths.async') + 'widget/' + key,
                type: 'GET',
                success: function (result) {
                    $('#widget-' + key).html(result);
                },
                error: function () {
                    console.log('failed to get widget');
                }
            });
        });
    },

    /*
     * Smarter dropdowns/dropups based on viewport height.
     * Based on: https://github.com/twbs/bootstrap/issues/3637#issuecomment-9850709
     *
     * @returns {undefined}
     */
    dropDowns: function () {
        $('[data-toggle="dropdown"]').each(function (index, item) {
            var mouseEvt;
            if (typeof event === 'undefined') {
                $(item).parent().click(function (e) {
                    mouseEvt = e;
                });
            } else {
                mouseEvt = event;
            }
            $(item).parent().on('show.bs.dropdown', function (e) {

                // Prevent breakage on old IE.
                if (typeof mouseEvt !== "undefined" && mouseEvt !== null) {
                    var self = $(this).find('[data-toggle="dropdown"]'),
                    menu = self.next('.dropdown-menu'),
                    mousey = mouseEvt.pageY + 20,
                    menuHeight = menu.height(),
                    menuVisY = $(window).height() - mousey + menuHeight, // Distance from the bottom of viewport
                    profilerHeight = 37; // The size of the Symfony Profiler Bar is 37px.

                    // The whole menu must fit when trying to 'dropup', but always prefer to 'dropdown' (= default).
                    if ((mousey - menuHeight) > 20 && menuVisY < profilerHeight) {
                        menu.css({
                            top: 'auto',
                            bottom: '100%'
                        });
                    }
                }

            });
        });
    },

    /*
     * Show 'dropzone' for jQuery file uploader.
     *
     * @returns {undefined}
     */
    dropZone: function () {
        // @todo make it prettier, and distinguish between '.in' and '.hover'.
        $(document).bind('dragover', function (e) {
            var dropZone = $('.dropzone'),
                timeout = window.dropZoneTimeout;
            if (!timeout) {
                dropZone.addClass('in');
            } else {
                clearTimeout(timeout);
            }
            if (e.target === dropZone[0]) {
                dropZone.addClass('hover');
            } else {
                dropZone.removeClass('hover');
            }
            window.dropZoneTimeout = setTimeout(function () {
                window.dropZoneTimeout = null;
                dropZone.removeClass('in hover');
            }, 100);
        });
    },

    /*
     * Initialize the Magnific popup shizzle. Fancybox is still here as a trigger, for backwards compatibility.
     */
    magnificPopup: function () {
        //
        $('.magnific, .fancybox').magnificPopup({
            type: 'image',
            gallery: {
                enabled: true
            },
            disableOn: 400,
            closeBtnInside: true,
            enableEscapeKey: true,
            mainClass: 'mfp-with-zoom',
            zoom: {
                enabled: true,
                duration: 300,
                easing: 'ease-in-out',
                opener: function (openerElement) {
                    return openerElement.parent().parent().find('img');
                }
            }
        });
    },

    /*
     * Initialize current status display setting focus on status select
     *
     * @returns {undefined}
     */
    focusStatusSelect: function () {
        $('#lastsavedstatus').click(function (e) {
            e.preventDefault();
            $('a[href="#tab-meta"]').click();
            $('#statusselect').focus();
        });
     },

    /*
     * Omnisearch
     *
     * @returns {undefined}
     */
    omnisearch: function () {
        $('.omnisearch').select2({
            placeholder: '',
            minimumInputLength: 3,
            multiple: true, // this is for better styling …
            ajax: {
                url: Bolt.conf('paths.async') + 'omnisearch',
                dataType: 'json',
                data: function (term, page) {
                    return {
                        q: term
                    };
                },
                results: function (data, page) {
                    var results = [];
                    $.each(data, function (index, item) {
                        results.push({
                            id: item.path,
                            path: item.path,
                            label: item.label,
                            priority: item.priority
                        });
                    });

                    return {results: results};
                }
            },
            formatResult: function (item) {
                var markup = '

' +
                    '' +
                    '
' + item.label + '
' +
                    '
' + item.path + '
' +
                    '

';

                return markup;
            },
            formatSelection: function (item) {
                window.location.href = item.path;

                return item.label;
            },
            dropdownCssClass: "bigdrop",
            escapeMarkup: function (m) {
                return m;
            }
        });
    },

    /*
     * Toggle options for showing / hiding the password input on the logon screen.
     *
     * @returns {undefined}
     */
    passwordInput: function () {
        $(".togglepass").on('click', function () {
            if ($(this).hasClass('show-password')) {
                $('input[name="password"]').attr('type', 'text');
                $('.togglepass.show-password').hide();
                $('.togglepass.hide-password').show();
            } else {
                $('input[name="password"]').attr('type', 'password');
                $('.togglepass.show-password').show();
                $('.togglepass.hide-password').hide();
            }
        });

        $('.login-forgot').bind('click', function (e) {
            $('.login-group, .password-group').hide();
            $('.reset-group').show();
        });

        $('.login-remembered').bind('click', function (e) {
            $('.login-group, .password-group').show();
            $('.reset-group').hide();
        });
    },

    /*
     * Initialize popovers.
     */
    popOvers: function () {
        $('.info-pop').popover({
            trigger: 'hover',
            delay: {
                show: 500,
                hide: 200
            }
        });
    },

    uploads: function () {
        $('input[data-upload]').each(function (item) {
            var data = $(this).data('upload'),
                accept = $(this).attr('accept').replace(/\./g, ''),
                autocomplete_conf;

            switch (data.type) {
                case 'Image':
                case 'File':
                    bindFileUpload(data.key);

                    autocomplete_conf = {
                        source: Bolt.conf('paths.async') + 'file/autocomplete?ext=' + encodeURIComponent(accept),
                        minLength: 2
                    };
                    if (data.type === 'Image') {
                        autocomplete_conf.close = function () {
                            var path = $('#field-' + data.key).val(),
                                url;

                            if (path) {
                                url = Bolt.conf('paths.root') +'thumbs/' + data.width + 'x' + data.height + 'c/' +
                                      encodeURI(path);
                            } else {
                                url = Bolt.conf('paths.app') + 'view/img/default_empty_4x3.png';
                            }
                            $('#thumbnail-' + data.key).html(
                                ''
                            );
                        };
                    }
                    $('#field-' + data.key).autocomplete(autocomplete_conf);
                    break;

                case 'ImageList':
                    Bolt.imagelist[data.key] = new FilelistHolder({id: data.key, type: data.type});
                    break;

                case 'FileList':
                    Bolt.filelist[data.key] = new FilelistHolder({id: data.key, type: data.type});
                    break;
            }
        });
    },

    /*
     * ?
     */
    sortables: function () {
        $('tbody.sortable').sortable({
            items: 'tr',
            opacity: '0.5',
            axis: 'y',
            handle: '.sorthandle',
            update: function (e, ui) {
                var serial = $(this).sortable('serialize');
                // Sorting request
                $.ajax({
                    url: $('#baseurl').attr('value') + 'content/sortcontent/' +
                        $(this).parent('table').data('contenttype'),
                    type: 'POST',
                    data: serial,
                    success: function (feedback) {
                        // Do nothing
                    }
                });
            }
        });
    }
};<|endoftext|>