<commit_before>import React, { Component } from 'react';
import {
    Text,
    View,
    Image,
    StyleSheet,
    TouchableOpacity,
    processColor,
    Platform,
    Dimensions,
} from 'react-native';
import {
    CombinedChart,
    ScatterChart,
    BarChart,
    LineChart,
} from 'react-native-charts-wrapper';
import * as Device from 'app/utility/Device';
import { SETTINGS_PANEL_STYLES } from 'app/appearance/styles/GlobalStyles';
import OneRMEditSetScreen from '../edit_set/OneRMEditSetScreen';

// TODO: confirm that this works on workout sets because workout sets dont have workoutIDs so wtf? but i saw it working so very confused

class OneRMChartView extends Component {

    constructor(props) {
        super(props);

        this.state = {
            setID: null,
            workoutID: null,
            selected: false,
            touchStarted: false,
            touchEnded: false,

            // cancels
            touchCanceled: false,
            multiTouchStart: false,
            multiSelect: false,
            pinchpan: false,
            dragged: false,

            // external check
            lastDrag: false,
        };
    }

    // TOUCHES

    componentWillReceiveProps(nextProps) {
        // NOTE: iOS only drag detection as cancelTouch doesn't fire on it
        if (Platform.OS === 'ios') {
            if (nextProps.dragged !== this.state.lastDrag) {
                this.setState({
                    lastDrag: nextProps.dragged,
                    dragged: true,
                });
            }
        }
    }

    componentDidUpdate(prevProps, prevState) {
        // a gesture is considered ended with a touch ended and a point was selected or it was pinched (as that can avoid select) or panned or dragged (can avoid select)
        if (Platform.OS === 'ios') {
            // iOS does not assume select happens before end, but it does assume that end ALWAYS happens
            var gestureEnded = this.state.touchEnded && (this.state.selected || this.state.pinchpan || this.state.dragged);
        } else {
            // android assumes select has to happen before end. If it isn't true, then weird things may happen
            var gestureEnded = (this.state.touchStarted && this.state.touchEnded) || this.state.multiTouchStart || this.state.pinchpan || this.state.dragged || this.state.multiSelect || this.state.touchCanceled;
        }

        // if gestured ended, there's a single touch, an actual point was selected, and it wasn't cancelled
        if (gestureEnded && this.state.touchStarted && this.state.selected && this.state.setID && !this.state.multiTouchStart && !this.state.pinchpan && !this.state.dragged && !this.state.multiSelect && !this.state.touchCanceled) {
            // tapped, open up the set
            this.props.tappedSet(this.state.setID, this.state.workoutID);
        }

        if (gestureEnded) {
            // reset the state
            this.setState({
                setID: null,
                workoutID: null,
                selected: false,
                touchStarted: false,
                touchEnded: false,
                touchCanceled: false,
                multiTouchStart: false,
                pinchpan: false,
                multiSelect: false,
                dragged: false,
            });
        }
    }

    _handleSelect(event) {
        const nativeEvent = event.nativeEvent;

        if (this.state.selected) {
            // multi select
            this.setState({
                multiSelect: true,
                setID: null,
                workoutID: null,
                selected: true,
            });
        } else if (nativeEvent.hasOwnProperty('data') && nativeEvent.data.hasOwnProperty('setID') && nativeEvent.data.hasOwnProperty('workoutID')) {
            // actually selected a datapoint
            this.setState({
                setID: nativeEvent.data.setID,
                workoutID: nativeEvent.data.workoutID,
                selected: true,
            });
        } else {
            // blank select
            this.setState({
                setID: null,
                workoutID: null,
                selected: true,
            });
        }
    }

    _handleTouchStart() {
        if (this.state.touchStarted) {
            // multi touch is zoom on android, but not necessarily so on iOS
            this.setState({
                multiTouchStart: true,
            });
        } else {
            this.setState({
                setID: null,
                workoutID: null,
                selected: false,
                touchStarted: true,
                touchEnded: false,
                touchCanceled: false,
                multiTouchStart: false,
                pinchpan: false,
                multiSelect: false,
                dragged: false,
            });
        }
    }

    _handleTouchEnd() {
        this.setState({
            touchEnded: true,
        });
    }

    _handleTouchCancel() {
        // android only touch cancel as iOS doesn't appear to ever cancel touches
        if (Platform.OS !== 'ios') {
            this.setState({
                touchCanceled: true,
            });
        }
    }

    _handleOnChange() {
        // iOS only pinch pan detection
        /// android doesn't need it as multi touch is zoom and pan doesn't cause issues on it due to order assumption
        if (Platform.OS === 'ios') {
            this.setState({
                pinchpan: true,
            });
        }
    }

    // RENDER

    _render1RM(r2) {
        if (this.props.shouldDisplayRegression) {
            let e1RM = this.props.e1RM ? this.props.e1RM : "---";
            let velocity = this.props.velocity ? this.props.velocity : "---";

            return (
                <View>
                    <View style={{flexDirection: 'row'}}>
                        <View>
                            <Text style={styles.oneRMText}>
                                e1RM:
                            </Text>
                        </View>
                        <View style={{flexDirection: 'column'}}>
                            <View>
                                <Text style={styles.oneRMValue}>
                                    {e1RM}
                                    <Text style={styles.oneRMMetric}>{this.props.metric}</Text>
                                </Text>
                            </View>
                            <View>
                                <Text style={{ textAlign: 'left', fontSize: 15}}>
                                    <Text style={styles.oneRMVelocityAt}>@ </Text>
                                    <Text style={styles.oneRMVelocity}>{velocity}</Text>
                                    <Text style={styles.oneRMVelocityMetric}>m/s</Text>
                                </Text>
                            </View>
                        </View>
                    </View>
                    <View>
                        <Text style={styles.r2Text}>r² : {this.props.r2}%</Text>
                    </View>
                </View>
            );
        } else {
            return (
                <View>
                    <Text style={styles.errorText}>
                        R² is too low, please clean up or log more data
                    </Text>
                </View>
            );
        }
    }

    _renderChartArea() {
        // TODO: need to check against ALL points rather than just active points to render a chart
        // NOTE: using > 1 for now as for active chart, I need that in order to get a domain
        if (this.props.activeChartData && this.props.activeChartData.length > 1) {
            return (
                <View>
                    {this._renderChart()}
                </View>
            );
        } else {
            const size = Device.isSmallDevice() ? 250 : 300;
            return <Image style={{width: size, height: size, marginTop: 5}} source={require('app/appearance/images/grayed_chart.png')} />
        }
    }

    _renderChart() {
        // set the data
        // NOTE: Android crashes if the data is length 0, so ONLY add it if data exists
        let data = {
            scatterData: {
                dataSets: []
            }
        };
        const dotMultiplier = Platform.OS === 'ios' ? 1 : 4;
        if (this.props.e1RM) {
            data.scatterData.dataSets.push({
                values: [{x: this.props.e1RM, y: this.props.velocity}],
                label: '1 Rep Max',

                config: {
                    drawValues: false,
                    colors: [processColor('black')],
                    highlightEnabled: false,
                    normalizeSizeEnabled: false,
                    scatterShape: 'CROSS',
                    scatterShapeSize: 25 * dotMultiplier,
                }

            });
        }
        if (this.props.unusedChartData && this.props.unusedChartData.length > 0) {
            data.scatterData.dataSets.push({
                values: this.props.unusedChartData,
                label: 'Unused Sets',
                config: {
                    colors: [processColor('rgba(47, 128, 237, 0.2)')],
                    drawValues: false,
                    highlightEnabled: false,
                    normalizeSizeEnabled: false,
                    scatterShape: 'CIRCLE',
                    drawHighlightIndicators: false,
                    scatterShapeSize: 12 * dotMultiplier,
                }
            });
        }
        if (this.props.errorChartData && this.props.errorChartData.length > 0) {
            data.scatterData.dataSets.push({
                values: this.props.errorChartData,
                label: 'Errors',
                config: {
                    colors: [processColor('red')],
                    drawValues: false,
                    highlightEnabled: true,
                    normalizeSizeEnabled: false,
                    highlightCircleWidth: 5,
                    scatterShape: 'CIRCLE',
                    drawHighlightIndicators: false,
                    scatterShapeSize: 12 * dotMultiplier,
                }
            });
        }
        if (this.props.activeChartData && this.props.activeChartData.length > 0) {
            data.scatterData.dataSets.push({
                values: this.props.activeChartData,
                label: 'Active Sets',
                config: {
                    colors: [processColor('rgba(47, 128, 237, 1)')],
                    drawValues: false,
                    highlightEnabled: true,
                    normalizeSizeEnabled: false,
                    highlightCircleWidth: 5,
                    scatterShape: 'CIRCLE',
                    drawHighlightIndicators: false,
                    scatterShapeSize: 12 * dotMultiplier,
                }
            });
        }
        if (this.props.regLeftPoint && this.props.regRightPoint) {
            data.lineData = {
                dataSets: [{
                    values: [this.props.regLeftPoint, this.props.regRightPoint],
                    label: 'Regression',

                    config: {
                        drawValues: false,
                        colors: [processColor('green')],
                        drawCircles: false,
                        lineWidth: 2,
                        highlightEnabled: false,
                    }
                }]
            };
        }

        return (
            <View>
                <CombinedChart
                    data={data}
                    xAxis={{
                        position: 'BOTTOM',
                        axisMinimum: this.props.minX,
                        axisMaximum: this.props.maxX,
                        textColor: processColor('rgba(77, 77, 77, 1)'),
                    }}
                    yAxis={{
                        right: {
                            drawLabels: false,
                            drawAxisLine: false,
                            drawGridLines: false,
                        },
                        left: {
                            axisMinimum: 0,
                            axisMaximum: this.props.maxY,
                            textColor: processColor('rgba(77, 77, 77, 1)'),
                        }
                    }}
                    marker={{enabled: true,
                        markerColor: processColor('rgba(47, 128, 237, 0.7)'),
                        textColor: processColor('white'),
                        markerFontSize: 14,
                    }}
                    pinchZoom={true}
                    maxVisibleValueCount={9001} // Assuming we'll never have this many sets
                    legend={{
                        enabled: true,
                        textColor: processColor('rgba(77, 77, 77, 1)')
                    }}
                    onSelect={this._handleSelect.bind(this)}
                    onTouchStart={this._handleTouchStart.bind(this)}
                    onTouchEnd={this._handleTouchEnd.bind(this)}
                    onTouchCancel={this._handleTouchCancel.bind(this)}
                    chartDescription={{text: ''}}
                    onChange={this._handleOnChange.bind(this)}
                    doubleTapToZoomEnabled={false}
                    style={styles.chart}/>
                    <OneRMEditSetScreen />
            </View>
        );
    }

    render() {
        return (
            <View style={ [SETTINGS_PANEL_STYLES.panel, { borderBottomWidth: 0, flexDirection: 'column', alignItems: 'center' }] }>
                <Text style={[styles.titleText, {marginBottom: 10}]}>Results</Text>
                {this._render1RM(this.props.r2)}
                <Text style={{textAlign: 'center', color: 'rgba(77, 77, 77, 1)', fontSize: 12}}>Tap a point to view and edit the data, then calculate again to see the updated 1RM</Text>
                {this._renderChartArea()}
                <TouchableOpacity style={{alignItems: 'center', marginBottom: 15, marginTop: 25}} onPress={ () => this.props.presentAlgorithm() }>
                    <Text style= {[SETTINGS_PANEL_STYLES.tappableText]} >How does the algorithm work?</Text>
                </TouchableOpacity>
                <TouchableOpacity style={{alignItems: 'center', marginBottom: 15}} onPress={ () => this.props.presentBestResults() }>
                    <Text style= {[SETTINGS_PANEL_STYLES.tappableText]} >How can I get the best results?</Text>
                </TouchableOpacity>
            </View>
        );
    }
}

const styles = StyleSheet.create({
    oneRMText: {
        color: 'rgba(77, 77, 77, 1)',
        marginTop: 10,
        marginBottom: 0,
        fontSize: 32,
        textAlign: 'left'
    },
    oneRMValue: {
        color: 'rgba(77, 77, 77, 1)',
        marginTop: 10,
        marginBottom: 0,
        fontSize: 32,
        textAlign: 'left',
        fontWeight: 'bold',
    },
    oneRMMetric: {
        color: 'rgba(77, 77, 77, 1)',
        fontSize: 15,
        textAlign: 'left'
    },
    oneRMVelocityAt: {
        color: 'rgba(77, 77, 77, 1)',
        fontSize: 15,
        textAlign: 'left'
    },
    oneRMVelocity: {
        color: 'rgba(77, 77, 77, 1)',
        fontSize: 15,
        textAlign: 'left',
        fontWeight: 'bold',
    },
    oneRMVelocityMetric: {
        color: 'rgba(77, 77, 77, 1)',
        fontSize: 10,
        textAlign: 'center'
    },
    r2Text: {
        color: 'rgba(77, 77, 77, 1)',
        marginBottom: 25,
        marginTop: 15,
        fontSize: 15,
        textAlign: 'center',
        fontWeight: 'bold',
    },
    errorText: {
        color: 'rgba(77, 77, 77, 1)',
        marginBottom: 20,
        fontSize: 18,
        textAlign: 'center',
    },
    titleText: {
        color: 'rgba(77, 77, 77, 1)',
        textAlign: 'center',
        fontSize: 20,
        fontWeight: 'bold',
    },
    chart: {
        // NOTE: flex is weird within scrollviews and charts
        // it's easier to just get it working by manually setting its size
        width: Dimensions.get('window').width - 45,
        height: (Dimensions.get('window').width - 45) * 1.25,
        justifyContent: 'center',
        alignItems: 'stretch',
        backgroundColor: 'transparent',
    },
});

export default OneRMChartView;
<commit_msg>fixed tap point issues regarding pan and pinch on ios<commit_after>import React, { Component } from 'react';
import {
    Text,
    View,
    Image,
    StyleSheet,
    TouchableOpacity,
    processColor,
    Platform,
    Dimensions,
} from 'react-native';
import {
    CombinedChart,
    ScatterChart,
    BarChart,
    LineChart,
} from 'react-native-charts-wrapper';
import * as Device from 'app/utility/Device';
import { SETTINGS_PANEL_STYLES } from 'app/appearance/styles/GlobalStyles';
import OneRMEditSetScreen from '../edit_set/OneRMEditSetScreen';

// TODO: confirm that this works on workout sets because workout sets dont have workoutIDs so wtf? but i saw it working so very confused

class OneRMChartView extends Component {

    constructor(props) {
        super(props);

        this.state = {
            setID: null,
            workoutID: null,
            selected: false,
            touchStarted: false,
            touchEnded: false,

            // cancels
            touchCanceled: false,
            multiTouchStart: false,
            multiSelect: false,
            pinchpan: false,
            dragged: false,

            // external check
            lastDrag: false,
        };
    }

    // TOUCHES

    componentWillReceiveProps(nextProps) {
        // NOTE: iOS only drag detection as cancelTouch doesn't fire on it
        if (Platform.OS === 'ios') {
            if (nextProps.dragged !== this.state.lastDrag) {
                this.setState({
                    lastDrag: nextProps.dragged,
                    dragged: true,
                });
            }
        }
    }

    componentDidUpdate(prevProps, prevState) {
        // a gesture is considered ended with a touch ended and a point was selected or it was pinched (as that can avoid select) or panned or dragged (can avoid select)
        if (Platform.OS === 'ios') {
            // iOS does not assume select happens before end, but it does assume that end ALWAYS happens
            var gestureEnded = this.state.touchEnded && (this.state.selected || this.state.pinchpan || this.state.dragged);
        } else {
            // android assumes select has to happen before end. If it isn't true, then weird things may happen
            var gestureEnded = (this.state.touchStarted && this.state.touchEnded) || this.state.multiTouchStart || this.state.pinchpan || this.state.dragged || this.state.multiSelect || this.state.touchCanceled;
        }

        // if gestured ended, there's a single touch, an actual point was selected, and it wasn't cancelled
        if (gestureEnded && this.state.touchStarted && this.state.selected && this.state.setID && !this.state.multiTouchStart && !this.state.pinchpan && !this.state.dragged && !this.state.multiSelect && !this.state.touchCanceled) {
            // tapped, open up the set
            this.props.tappedSet(this.state.setID, this.state.workoutID);
        }

        if (gestureEnded) {
            // reset the state
            this.setState({
                setID: null,
                workoutID: null,
                selected: false,
                touchStarted: false,
                touchEnded: false,
                touchCanceled: false,
                multiTouchStart: false,
                pinchpan: false,
                multiSelect: false,
                dragged: false,
            });
        }
    }

    _handleSelect(event) {
        // sanity check for delayed actions
        const nativeEvent = event.nativeEvent;

        if (this.state.selected) {
            // multi select
            this.setState({
                multiSelect: true,
                setID: null,
                workoutID: null,
                selected: true,
            });
        } else if (nativeEvent.hasOwnProperty('data') && nativeEvent.data.hasOwnProperty('setID') && nativeEvent.data.hasOwnProperty('workoutID')) {
            // actually selected a datapoint
            this.setState({
                setID: nativeEvent.data.setID,
                workoutID: nativeEvent.data.workoutID,
                selected: true,
            });
        } else {
            // blank select
            this.setState({
                setID: null,
                workoutID: null,
                selected: true,
            });
        }
    }

    _handleTouchStart() {
        if (this.state.touchStarted) {
            // multi touch is zoom on android, but not necessarily so on iOS
            this.setState({
                multiTouchStart: true,
            });
        } else {
            this.setState({
                setID: null,
                workoutID: null,
                selected: false,
                touchStarted: true,
                touchEnded: false,
                touchCanceled: false,
                multiTouchStart: false,
                pinchpan: false,
                multiSelect: false,
                dragged: false,
            });
        }
    }

    _handleTouchEnd() {
        this.setState({
            touchEnded: true,
        });
    }

    _handleTouchCancel() {
        // android only touch cancel as iOS doesn't appear to ever cancel touches
        if (Platform.OS !== 'ios') {
            this.setState({
                touchCanceled: true,
            });
        }
    }

    _handleOnChange() {
        // iOS only pinch pan detection
        /// android doesn't need it as multi touch is zoom and pan doesn't cause issues on it due to order assumption
        if (Platform.OS === 'ios' && this.state.touchStarted) {
            this.setState({
                pinchpan: true,
            });
        }
    }

    // RENDER

    _render1RM(r2) {
        if (this.props.shouldDisplayRegression) {
            let e1RM = this.props.e1RM ? this.props.e1RM : "---";
            let velocity = this.props.velocity ? this.props.velocity : "---";

            return (
                <View>
                    <View style={{flexDirection: 'row'}}>
                        <View>
                            <Text style={styles.oneRMText}>
                                e1RM:
                            </Text>
                        </View>
                        <View style={{flexDirection: 'column'}}>
                            <View>
                                <Text style={styles.oneRMValue}>
                                    {e1RM}
                                    <Text style={styles.oneRMMetric}>{this.props.metric}</Text>
                                </Text>
                            </View>
                            <View>
                                <Text style={{ textAlign: 'left', fontSize: 15}}>
                                    <Text style={styles.oneRMVelocityAt}>@ </Text>
                                    <Text style={styles.oneRMVelocity}>{velocity}</Text>
                                    <Text style={styles.oneRMVelocityMetric}>m/s</Text>
                                </Text>
                            </View>
                        </View>
                    </View>
                    <View>
                        <Text style={styles.r2Text}>r² : {this.props.r2}%</Text>
                    </View>
                </View>
            );
        } else {
            return (
                <View>
                    <Text style={styles.errorText}>
                        R² is too low, please clean up or log more data
                    </Text>
                </View>
            );
        }
    }

    _renderChartArea() {
        // TODO: need to check against ALL points rather than just active points to render a chart
        // NOTE: using > 1 for now as for active chart, I need that in order to get a domain
        if (this.props.activeChartData && this.props.activeChartData.length > 1) {
            return (
                <View>
                    {this._renderChart()}
                </View>
            );
        } else {
            const size = Device.isSmallDevice() ? 250 : 300;
            return <Image style={{width: size, height: size, marginTop: 5}} source={require('app/appearance/images/grayed_chart.png')} />
        }
    }

    _renderChart() {
        // set the data
        // NOTE: Android crashes if the data is length 0, so ONLY add it if data exists
        let data = {
            scatterData: {
                dataSets: []
            }
        };
        const dotMultiplier = Platform.OS === 'ios' ? 1 : 4;
        if (this.props.e1RM) {
            data.scatterData.dataSets.push({
                values: [{x: this.props.e1RM, y: this.props.velocity}],
                label: '1 Rep Max',

                config: {
                    drawValues: false,
                    colors: [processColor('black')],
                    highlightEnabled: false,
                    normalizeSizeEnabled: false,
                    scatterShape: 'CROSS',
                    scatterShapeSize: 25 * dotMultiplier,
                }

            });
        }
        if (this.props.unusedChartData && this.props.unusedChartData.length > 0) {
            data.scatterData.dataSets.push({
                values: this.props.unusedChartData,
                label: 'Unused Sets',
                config: {
                    colors: [processColor('rgba(47, 128, 237, 0.2)')],
                    drawValues: false,
                    highlightEnabled: false,
                    normalizeSizeEnabled: false,
                    scatterShape: 'CIRCLE',
                    drawHighlightIndicators: false,
                    scatterShapeSize: 12 * dotMultiplier,
                }
            });
        }
        if (this.props.errorChartData && this.props.errorChartData.length > 0) {
            data.scatterData.dataSets.push({
                values: this.props.errorChartData,
                label: 'Errors',
                config: {
                    colors: [processColor('red')],
                    drawValues: false,
                    highlightEnabled: true,
                    normalizeSizeEnabled: false,
                    highlightCircleWidth: 5,
                    scatterShape: 'CIRCLE',
                    drawHighlightIndicators: false,
                    scatterShapeSize: 12 * dotMultiplier,
                }
            });
        }
        if (this.props.activeChartData && this.props.activeChartData.length > 0) {
            data.scatterData.dataSets.push({
                values: this.props.activeChartData,
                label: 'Active Sets',
                config: {
                    colors: [processColor('rgba(47, 128, 237, 1)')],
                    drawValues: false,
                    highlightEnabled: true,
                    normalizeSizeEnabled: false,
                    highlightCircleWidth: 5,
                    scatterShape: 'CIRCLE',
                    drawHighlightIndicators: false,
                    scatterShapeSize: 12 * dotMultiplier,
                }
            });
        }
        if (this.props.regLeftPoint && this.props.regRightPoint) {
            data.lineData = {
                dataSets: [{
                    values: [this.props.regLeftPoint, this.props.regRightPoint],
                    label: 'Regression',

                    config: {
                        drawValues: false,
                        colors: [processColor('green')],
                        drawCircles: false,
                        lineWidth: 2,
                        highlightEnabled: false,
                    }
                }]
            };
        }

        return (
            <View>
                <CombinedChart
                    data={data}
                    xAxis={{
                        position: 'BOTTOM',
                        axisMinimum: this.props.minX,
                        axisMaximum: this.props.maxX,
                        textColor: processColor('rgba(77, 77, 77, 1)'),
                    }}
                    yAxis={{
                        right: {
                            drawLabels: false,
                            drawAxisLine: false,
                            drawGridLines: false,
                        },
                        left: {
                            axisMinimum: 0,
                            axisMaximum: this.props.maxY,
                            textColor: processColor('rgba(77, 77, 77, 1)'),
                        }
                    }}
                    marker={{enabled: true,
                        markerColor: processColor('rgba(47, 128, 237, 0.7)'),
                        textColor: processColor('white'),
                        markerFontSize: 14,
                    }}
                    pinchZoom={true}
                    maxVisibleValueCount={9001} // Assuming we'll never have this many sets
                    legend={{
                        enabled: true,
                        textColor: processColor('rgba(77, 77, 77, 1)')
                    }}
                    dragDecelerationEnabled={false}
                    onSelect={this._handleSelect.bind(this)}
                    onTouchStart={this._handleTouchStart.bind(this)}
                    onTouchEnd={this._handleTouchEnd.bind(this)}
                    onTouchCancel={this._handleTouchCancel.bind(this)}
                    chartDescription={{text: ''}}
                    onChange={this._handleOnChange.bind(this)}
                    doubleTapToZoomEnabled={false}
                    style={styles.chart}/>
                    <OneRMEditSetScreen />
            </View>
        );
    }

    render() {
        return (
            <View style={ [SETTINGS_PANEL_STYLES.panel, { borderBottomWidth: 0, flexDirection: 'column', alignItems: 'center' }] }>
                <Text style={[styles.titleText, {marginBottom: 10}]}>Results</Text>
                {this._render1RM(this.props.r2)}
                <Text style={{textAlign: 'center', color: 'rgba(77, 77, 77, 1)', fontSize: 12}}>Tap a point to view and edit the data, then calculate again to see the updated 1RM</Text>
                {this._renderChartArea()}
                <TouchableOpacity style={{alignItems: 'center', marginBottom: 15, marginTop: 25}} onPress={ () => this.props.presentAlgorithm() }>
                    <Text style= {[SETTINGS_PANEL_STYLES.tappableText]} >How does the algorithm work?</Text>
                </TouchableOpacity>
                <TouchableOpacity style={{alignItems: 'center', marginBottom: 15}} onPress={ () => this.props.presentBestResults() }>
                    <Text style= {[SETTINGS_PANEL_STYLES.tappableText]} >How can I get the best results?</Text>
                </TouchableOpacity>
            </View>
        );
    }
}

const styles = StyleSheet.create({
    oneRMText: {
        color: 'rgba(77, 77, 77, 1)',
        marginTop: 10,
        marginBottom: 0,
        fontSize: 32,
        textAlign: 'left'
    },
    oneRMValue: {
        color: 'rgba(77, 77, 77, 1)',
        marginTop: 10,
        marginBottom: 0,
        fontSize: 32,
        textAlign: 'left',
        fontWeight: 'bold',
    },
    oneRMMetric: {
        color: 'rgba(77, 77, 77, 1)',
        fontSize: 15,
        textAlign: 'left'
    },
    oneRMVelocityAt: {
        color: 'rgba(77, 77, 77, 1)',
        fontSize: 15,
        textAlign: 'left'
    },
    oneRMVelocity: {
        color: 'rgba(77, 77, 77, 1)',
        fontSize: 15,
        textAlign: 'left',
        fontWeight: 'bold',
    },
    oneRMVelocityMetric: {
        color: 'rgba(77, 77, 77, 1)',
        fontSize: 10,
        textAlign: 'center'
    },
    r2Text: {
        color: 'rgba(77, 77, 77, 1)',
        marginBottom: 25,
        marginTop: 15,
        fontSize: 15,
        textAlign: 'center',
        fontWeight: 'bold',
    },
    errorText: {
        color: 'rgba(77, 77, 77, 1)',
        marginBottom: 20,
        fontSize: 18,
        textAlign: 'center',
    },
    titleText: {
        color: 'rgba(77, 77, 77, 1)',
        textAlign: 'center',
        fontSize: 20,
        fontWeight: 'bold',
    },
    chart: {
        // NOTE: flex is weird within scrollviews and charts
        // it's easier to just get it working by manually setting its size
        width: Dimensions.get('window').width - 45,
        height: (Dimensions.get('window').width - 45) * 1.25,
        justifyContent: 'center',
        alignItems: 'stretch',
        backgroundColor: 'transparent',
    },
});

export default OneRMChartView;
<|endoftext|>


<commit_before>define(['lodash'], function(_) {
    'use strict';
    return ['$stateParams', '$scope', 'Restangular', '$state', '$filter', function($stateParams, $scope, Restangular, $state, $filter) {
        /**
         * Show or hide the form for a new contact person for this company
         * @param show true/false = show/hide
         */
        $scope.doShowContactPersonForm = function(show) {
            $scope.showContactPersonForm = show;
        };

        /**
         * POST the entered contact person to the server.
         * This method adds the company relationship to the contact person.
         */
        $scope.saveNewContactPerson = function() {
            //Add the company relationship to the person
            $scope.newContactPerson.company = $scope.company._links.self.href;
            Restangular.all('contactPersons').post($scope.newContactPerson).then(function(result) {
                $scope.newContactPerson = {};
                $scope.contactPersons.push(result);
                $scope.doShowContactPersonForm(false);
            }, function(response) {
                $scope.contactPersonErrors = response.data.errors;
            });
        };

        /**
         * Delete a contact person from this company by issuing a DELETE to the API
         * @param contactPerson The contact person to delete
         */
        $scope.removeContactPerson = function(contactPerson) {
            /*
             We cant use the contactPerson object directly because it is tied to the company.
             Restangular would execute DELETE '/api/companies/0/contactPersons/0' which is not what we want
             */
            Restangular.one('contactPersons', contactPerson.id).remove().then(function() {
                _.remove($scope.contactPersons, function(cP) {
                    return cP.id === contactPerson.id;
                });
            });
        };

        $scope.companyIdChanged = function() {
            $state.go('trackr.administration.companies.edit', {id: $scope.company.companyId});
        };

        $scope.companyIdError = function(response) {
            if(response.status === 409) {
                $scope.companyIdErrorText =  $filter('translate')('COMPANY.COMPANY_ID_CONFLICT');
            }
        };

        /*
         Initialization of $scope objects
         */
        $scope.contactPersonErrors = [];
        $scope.newContactPerson = {};
        $scope.doShowContactPersonForm(false);
        Restangular.allUrl('companies', 'api/companies/search/findByCompanyId').getList({companyId: $stateParams.id}).then(function(companies) {
            //TODO: why does spring return an array? The method signature is a single entity.
            var company = companies[0];

            $scope.company = company;
            company.customGET('address').then(function(address) {
                $scope.address = address;
            });
            company.getList('contactPersons').then(function(contactPersons) {
                $scope.contactPersons = contactPersons;
            });
        });
    }];
});<commit_msg>TRACKR-88 - List panel should be updated upon identifier changes Company ID in the list gets updated.<commit_after>define(['lodash'], function(_) {
    'use strict';
    return ['$stateParams', '$scope', 'Restangular', '$state', '$filter', function($stateParams, $scope, Restangular, $state, $filter) {
        $scope.errors = [];
        /**
         * Show or hide the form for a new contact person for this company
         * @param show true/false = show/hide
         */
        $scope.doShowContactPersonForm = function(show) {
            $scope.showContactPersonForm = show;
        };

        /**
         * POST the entered contact person to the server.
         * This method adds the company relationship to the contact person.
         */
        $scope.saveNewContactPerson = function() {
            //Add the company relationship to the person
            $scope.newContactPerson.company = $scope.company._links.self.href;
            Restangular.all('contactPersons').post($scope.newContactPerson).then(function(result) {
                $scope.newContactPerson = {};
                $scope.contactPersons.push(result);
                $scope.doShowContactPersonForm(false);
            }, function(response) {
                $scope.contactPersonErrors = response.data.errors;
            });
        };

        /**
         * Delete a contact person from this company by issuing a DELETE to the API
         * @param contactPerson The contact person to delete
         */
        $scope.removeContactPerson = function(contactPerson) {
            /*
             We cant use the contactPerson object directly because it is tied to the company.
             Restangular would execute DELETE '/api/companies/0/contactPersons/0' which is not what we want
             */
            Restangular.one('contactPersons', contactPerson.id).remove().then(function() {
                _.remove($scope.contactPersons, function(cP) {
                    return cP.id === contactPerson.id;
                });
            });
        };

        $scope.companyIdChanged = function() {
            //Change the company id in the list in the parent controller
            var companyInList = _.find($scope.$parent.companies, {id: $scope.company.id});
            companyInList.companyId = $scope.company.companyId;
            //reload so the url is correct
            $state.go('trackr.administration.companies.edit', {id: $scope.company.companyId});
        };

        $scope.companyIdError = function(response) {
            if(response.status === 409) {
                $scope.companyIdErrorText =  $filter('translate')('COMPANY.COMPANY_ID_CONFLICT');
            }
        };

        /*
         Initialization of $scope objects
         */
        $scope.contactPersonErrors = [];
        $scope.newContactPerson = {};
        $scope.doShowContactPersonForm(false);
        Restangular.allUrl('companies', 'api/companies/search/findByCompanyId').getList({companyId: $stateParams.id}).then(function(companies) {
            //TODO: why does spring return an array? The method signature is a single entity.
            var company = companies[0];

            $scope.company = company;
            company.customGET('address').then(function(address) {
                $scope.address = address;
            });
            company.getList('contactPersons').then(function(contactPersons) {
                $scope.contactPersons = contactPersons;
            });
        });
    }];
});<|endoftext|>


<commit_before>import React from 'react';
import SPELLS from 'common/SPELLS';
import SpellLink from 'common/SpellLink';
import SpellIcon from 'common/SpellIcon';
import Wrapper from 'common/Wrapper';
import { formatNumber, formatPercentage } from 'common/format';
import StatisticBox, { STATISTIC_ORDER } from 'Main/StatisticBox';
import Combatants from 'Parser/Core/Modules/Combatants';
import AbilityTracker from 'Parser/Core/Modules/AbilityTracker';
import Analyzer from 'Parser/Core/Analyzer';
import calculateEffectiveDamage from 'Parser/Core/calculateEffectiveDamage';

const DAMAGE_BONUS = 0.4;
const RUNE_DURATION = 10;
const INCANTERS_FLOW_EXPECTED_BOOST = 0.12;

// FIXME due to interactions with Ignite, the damage boost number will be underrated for Fire Mages. Still fine for Arcane and Frost.
class RuneOfPower extends Analyzer {
  static dependencies = {
    combatants: Combatants,
    abilityTracker: AbilityTracker,
  };

  damage = 0;

  on_initialized() {
    this.active = this.combatants.selected.hasTalent(SPELLS.RUNE_OF_POWER_TALENT.id);
  }

  on_byPlayer_damage(event) {
    if (this.combatants.selected.hasBuff(SPELLS.RUNE_OF_POWER_BUFF.id)) {
      this.damage += calculateEffectiveDamage(event, DAMAGE_BONUS);
    }
  }

  get damagePercent() {
    return this.owner.getPercentageOfTotalDamageDone(this.damage);
  }

  get damageIncreasePercent() {
    return this.damagePercent / (1 - this.damagePercent);
  }

  get uptimeMS() {
    return this.combatants.selected.getBuffUptime(SPELLS.RUNE_OF_POWER_BUFF.id);
  }

  get roundedSecondsPerCast() {
    return ((this.uptimeMS / this.abilityTracker.getAbility(SPELLS.RUNE_OF_POWER_BUFF.id).casts) / 1000).toFixed(1);
  }

  get damageSuggestionThresholds() {
    return {
      actual: this.damageIncreasePercent,
      isLessThan: {
        minor: INCANTERS_FLOW_EXPECTED_BOOST,
        average: INCANTERS_FLOW_EXPECTED_BOOST,
        major: INCANTERS_FLOW_EXPECTED_BOOST - 0.03,
      },
      style: 'percentage',
    };
  }

  get roundedSecondsSuggestionThresholds() {
    return {
      actual: this.damageIncreasePercent,
      isLessThan: {
        minor: RUNE_DURATION,
        average: RUNE_DURATION - 1,
        major: RUNE_DURATION - 3,
      },
      style: 'percentage',
    };
  }

  suggestions(when) {
    when(this.damageSuggestionThresholds)
      .addSuggestion((suggest, actual, recommended) => {
        return suggest(<Wrapper>Your <SpellLink id={SPELLS.RUNE_OF_POWER_TALENT.id} /> damage boost is below the expected passive gain from <SpellLink id={SPELLS.INCANTERS_FLOW_TALENT.id} />. Either find ways to make better use of the talent, or switch to <SpellLink id={SPELLS.INCANTERS_FLOW_TALENT.id} />.</Wrapper>)
          .icon(SPELLS.RUNE_OF_POWER_TALENT.icon)
          .actual(`${formatPercentage(this.damageIncreasePercent)}% damage increase from Rune of Power`)
          .recommended(`${formatPercentage(recommended)}% is the passive gain from Incanter's Flow`);
      });
    if (this.abilityTracker.getAbility(SPELLS.RUNE_OF_POWER_BUFF.id).casts < 1) {
      when(this.roundedSecondsSuggestionThresholds)
        .addSuggestion((suggest, actual, recommended) => {
          return suggest(<Wrapper>You sometimes aren't standing in your <SpellLink id={SPELLS.RUNE_OF_POWER_TALENT.id} /> for its full duration. Try to only use it when you know you won't have to move for the duration of the effect.</Wrapper>)
            .icon(SPELLS.RUNE_OF_POWER_TALENT.icon)
            .actual(`Average ${this.roundedSecondsPerCast}s standing in each Rune of Power`)
            .recommended(`the full duration of ${formatNumber(RUNE_DURATION)}s is recommended`);
        });
    }
  }

  statistic() {
    return (
      <StatisticBox
        icon={<SpellIcon id={SPELLS.RUNE_OF_POWER_TALENT.id} />}
        value={`${formatPercentage(this.damagePercent)} %`}
        label="Rune of Power damage"
        tooltip={`This is the portion of your total damage attributable to Rune of Power's boost. Expressed as an increase vs never using Rune of Power, this is a ${formatPercentage(this.damageIncreasePercent)}% damage increase. Note that this number does not factor in the opportunity cost of casting Rune of Power instead of another damaging spell`}
      />
    );
  }
  statisticOrder = STATISTIC_ORDER.OPTIONAL(100);
}

export default RuneOfPower;
<commit_msg>fixed suggestions<commit_after>import React from 'react';
import SPELLS from 'common/SPELLS';
import SpellLink from 'common/SpellLink';
import SpellIcon from 'common/SpellIcon';
import Wrapper from 'common/Wrapper';
import { formatNumber, formatPercentage } from 'common/format';
import StatisticBox, { STATISTIC_ORDER } from 'Main/StatisticBox';
import Combatants from 'Parser/Core/Modules/Combatants';
import AbilityTracker from 'Parser/Core/Modules/AbilityTracker';
import Analyzer from 'Parser/Core/Analyzer';
import calculateEffectiveDamage from 'Parser/Core/calculateEffectiveDamage';

const DAMAGE_BONUS = 0.4;
const RUNE_DURATION = 10;
const INCANTERS_FLOW_EXPECTED_BOOST = 0.12;

// FIXME due to interactions with Ignite, the damage boost number will be underrated for Fire Mages. Still fine for Arcane and Frost.
class RuneOfPower extends Analyzer {
  static dependencies = {
    combatants: Combatants,
    abilityTracker: AbilityTracker,
  };

  damage = 0;

  on_initialized() {
    this.active = this.combatants.selected.hasTalent(SPELLS.RUNE_OF_POWER_TALENT.id);
  }

  on_byPlayer_damage(event) {
    if (this.combatants.selected.hasBuff(SPELLS.RUNE_OF_POWER_BUFF.id)) {
      this.damage += calculateEffectiveDamage(event, DAMAGE_BONUS);
    }
  }

  get damagePercent() {
    return this.owner.getPercentageOfTotalDamageDone(this.damage);
  }

  get damageIncreasePercent() {
    return this.damagePercent / (1 - this.damagePercent);
  }

  get uptimeMS() {
    return this.combatants.selected.getBuffUptime(SPELLS.RUNE_OF_POWER_BUFF.id);
  }

  get roundedSecondsPerCast() {
    return ((this.uptimeMS / this.abilityTracker.getAbility(SPELLS.RUNE_OF_POWER_BUFF.id).casts) / 1000).toFixed(1);
  }

  get damageSuggestionThresholds() {
    return {
      actual: this.damageIncreasePercent,
      isLessThan: {
        minor: INCANTERS_FLOW_EXPECTED_BOOST,
        average: INCANTERS_FLOW_EXPECTED_BOOST,
        major: INCANTERS_FLOW_EXPECTED_BOOST - 0.03,
      },
      style: 'percentage',
    };
  }

  get roundedSecondsSuggestionThresholds() {
    return {
      actual: this.roundedSecondsPerCast,
      isLessThan: {
        minor: RUNE_DURATION,
        average: RUNE_DURATION - 1,
        major: RUNE_DURATION - 3,
      },
      style: 'percentage',
    };
  }

  suggestions(when) {
    when(this.damageSuggestionThresholds)
      .addSuggestion((suggest, actual, recommended) => {
        return suggest(<Wrapper>Your <SpellLink id={SPELLS.RUNE_OF_POWER_TALENT.id} /> damage boost is below the expected passive gain from <SpellLink id={SPELLS.INCANTERS_FLOW_TALENT.id} />. Either find ways to make better use of the talent, or switch to <SpellLink id={SPELLS.INCANTERS_FLOW_TALENT.id} />.</Wrapper>)
          .icon(SPELLS.RUNE_OF_POWER_TALENT.icon)
          .actual(`${formatPercentage(this.damageIncreasePercent)}% damage increase from Rune of Power`)
          .recommended(`${formatPercentage(recommended)}% is the passive gain from Incanter's Flow`);
      });
    if (this.abilityTracker.getAbility(SPELLS.RUNE_OF_POWER_BUFF.id).casts > 0) {
      when(this.roundedSecondsSuggestionThresholds)
        .addSuggestion((suggest, actual, recommended) => {
          return suggest(<Wrapper>You sometimes aren't standing in your <SpellLink id={SPELLS.RUNE_OF_POWER_TALENT.id} /> for its full duration. Try to only use it when you know you won't have to move for the duration of the effect.</Wrapper>)
            .icon(SPELLS.RUNE_OF_POWER_TALENT.icon)
            .actual(`Average ${this.roundedSecondsPerCast}s standing in each Rune of Power`)
            .recommended(`the full duration of ${formatNumber(RUNE_DURATION)}s is recommended`);
        });
    }
  }

  statistic() {
    return (
      <StatisticBox
        icon={<SpellIcon id={SPELLS.RUNE_OF_POWER_TALENT.id} />}
        value={`${formatPercentage(this.damagePercent)} %`}
        label="Rune of Power damage"
        tooltip={`This is the portion of your total damage attributable to Rune of Power's boost. Expressed as an increase vs never using Rune of Power, this is a ${formatPercentage(this.damageIncreasePercent)}% damage increase. Note that this number does not factor in the opportunity cost of casting Rune of Power instead of another damaging spell`}
      />
    );
  }
  statisticOrder = STATISTIC_ORDER.OPTIONAL(100);
}

export default RuneOfPower;
<|endoftext|>


<commit_before>ManageIQ.angular.app.component('widgetFooter', {
  bindings: {
    widgetLastRun: '@',
    widgetNextRun: '@',
  },
  controllerAs: 'vm',
  template: `

      ${__('Updated')}
      {{vm.widgetLastRun}}
      |
      ${__('Next')}
      {{vm.widgetNextRun}}

  `,
});
<commit_msg>Changed wording for consistency<commit_after>ManageIQ.angular.app.component('widgetFooter', {
  bindings: {
    widgetLastRun: '@',
    widgetNextRun: '@',
  },
  controllerAs: 'vm',
  template: `

      ${__('Updated On')}
      {{vm.widgetLastRun}}
      |
      ${__('Next Update On')}
      {{vm.widgetNextRun}}

  `,
});
<|endoftext|>


<commit_before>'use strict';

/**
 * Import plugins
 */
var gulp = require('gulp'),
    $ = require('gulp-load-plugins')(),
    browserSync = require('browser-sync'),
    reload = browserSync.reload,
    runSequence = require('run-sequence'),
    argv = require('yargs').argv,
    del = require('del');

/**
 * Build vendors dependencies
 */
gulp.task('vendors', function() {

  /**
   * CSS VENDORS
   */
  gulp.src([
        ''
      ])
      .pipe($.concat('vendors.css'))
      .pipe($.minifyCss())
      .pipe(gulp.dest('build/css'));

  /**
   * JS VENDORS
   * (with jQuery and Bootstrap dependencies first)
   */

  gulp.src([
      'bower_components/jquery/dist/jquery.js',
      'bower_components/bootstrap-sass-official/assets/javascripts/bootstrap/affix.js',
      'bower_components/bootstrap-sass-official/assets/javascripts/bootstrap/alert.js',
      'bower_components/bootstrap-sass-official/assets/javascripts/bootstrap/button.js',
      'bower_components/bootstrap-sass-official/assets/javascripts/bootstrap/carousel.js',
      'bower_components/bootstrap-sass-official/assets/javascripts/bootstrap/collapse.js',
      'bower_components/bootstrap-sass-official/assets/javascripts/bootstrap/dropdown.js',
      'bower_components/bootstrap-sass-official/assets/javascripts/bootstrap/modal.js',
      'bower_components/bootstrap-sass-official/assets/javascripts/bootstrap/tooltip.js',
      'bower_components/bootstrap-sass-official/assets/javascripts/bootstrap/popover.js',
      'bower_components/bootstrap-sass-official/assets/javascripts/bootstrap/scrollspy.js',
      'bower_components/bootstrap-sass-official/assets/javascripts/bootstrap/tab.js',
      'bower_components/bootstrap-sass-official/assets/javascripts/bootstrap/transition.js'
    ])
    .pipe($.concat('vendors.min.js'))
    .pipe($.uglify())
    .pipe(gulp.dest('build/js'));

  /**
   * FONTS SOURCES
   * Important to add the bootstrap fonts to avoid issues with the fonts include path
   */
  gulp.src([
      'bower_components/bootstrap-sass-official/assets/fonts/bootstrap/*',
      'assets/fonts/*'
    ])
    .pipe(gulp.dest('build/fonts'));

  /**
   * POLYFILLS SOURCES
   * Various polyfills required for old IE
   */
  gulp.src([
      'bower_components/html5shiv/dist/html5shiv.js',
      'bower_components/respond/dest/respond.src.js'
    ])
    .pipe($.concat('polyfills.min.js'))
    .pipe($.uglify())
    .pipe(gulp.dest('build/js'));
});

/**
 * Build styles from SCSS files
 * With error reporting on compiling (so that there's no crash)
 */
gulp.task('styles', function() {
  if (argv.production) { console.log('[styles] Processing styles for production env.' ); }
  else { console.log('[styles] Processing styles for dev env. No minifying here, for sourcemaps!') }
  return gulp.src('assets/sass/main.scss')
    .pipe($.rubySass({style: 'compact', 'sourcemap=none': true}))
      .on('error', $.notify.onError(function (error) {
         console.log(error.message);
         if (!argv.production) {
           return 'Message to the notifier: ' + error.message;
         }
      }))
    .pipe($.sourcemaps.init())
    .pipe($.autoprefixer({
      browsers: ['last 2 versions', 'safari 5', 'ie 8', 'ie 9', 'ff 27', 'opera 12.1'],
      cascade: false
    }))
    .pipe($.if(argv.production, $.minifyCss()))
    .pipe($.sourcemaps.write('.'))
    .pipe(gulp.dest('build/css'));
});

/**
 * Build JS
 * With error reporting on compiling (so that there's no crash)
 * And jshint check to highlight errors as we go.
 */
gulp.task('scripts', function() {
  return gulp.src('assets/js/*.js')
    .pipe($.jshint())
    .pipe($.jshint.reporter('jshint-stylish'))
    .pipe($.concat('main.js'))
    .pipe(gulp.dest('build/js'))
    .pipe($.rename({ suffix: '.min' }))
    .pipe($.uglify())
    .pipe(gulp.dest('build/js'));
});

/**
 * Build Hologram Styleguide
 */
gulp.task('styleguide', function () {
  return gulp.src('hologram_config.yml')
    .pipe($.hologram());
});

<% if (useTwig) { %>
/**
 * Compile TWIG example pages
 */

gulp.task('twig', function () {
    return gulp.src('assets/pages/*.twig')
        .pipe($.twig())
        .pipe(gulp.dest('styleguide/pages'));
});
<% } %>

/**
 * Clean output directories
 */
gulp.task('clean', del.bind(null, ['build', 'styleguide']));

/**
 * Serve
 */
gulp.task('serve', ['styles', 'scripts'<% if (useTwig) { %>, 'twig'<% } %>], function () {
  browserSync({
    server: {
      baseDir: ['styleguide'],
    },
    open: false
  });
  gulp.watch(['styleguide/*.html'], reload);
  gulp.watch(['assets/sass/**/*.scss'], function() {
    runSequence('styles', 'styleguide', reload);
  });
  gulp.watch(['assets/js/**/*.js'], function() {
    runSequence('scripts', reload);
  });
  <% if (useTwig) { %>
  gulp.watch(['assets/pages/**/*'], function() {
    // clean folder before compiling
    del.bind(null, ['styleguide/pages'])
    runSequence('twig', reload);
  });
  <% } %>
});

/**
 * Deploy to GH pages
 */

gulp.task('deploy', function () {
  gulp.src("styleguide/**/*")
    .pipe($.ghPages());
});

/**
 * Task to build assets on production server
 */
gulp.task('build',['clean'], function() {
    argv.production = true;
    runSequence('vendors', 'styles', 'scripts');
});

/**
 * Default task
 */
gulp.task('default', ['clean'], function(cb) {
  runSequence('vendors', 'styles', 'scripts',<% if (useTwig) { %>'twig',<% } %> 'styleguide', cb);
});

<commit_msg>add condition if arg production for sourcemaps<commit_after>'use strict';

/**
 * Import plugins
 */
var gulp = require('gulp'),
    $ = require('gulp-load-plugins')(),
    browserSync = require('browser-sync'),
    reload = browserSync.reload,
    runSequence = require('run-sequence'),
    argv = require('yargs').argv,
    del = require('del');

/**
 * Build vendors dependencies
 */
gulp.task('vendors', function() {

  /**
   * CSS VENDORS
   */
  gulp.src([
        ''
      ])
      .pipe($.concat('vendors.css'))
      .pipe($.minifyCss())
      .pipe(gulp.dest('build/css'));

  /**
   * JS VENDORS
   * (with jQuery and Bootstrap dependencies first)
   */

  gulp.src([
      'bower_components/jquery/dist/jquery.js',
      'bower_components/bootstrap-sass-official/assets/javascripts/bootstrap/affix.js',
      'bower_components/bootstrap-sass-official/assets/javascripts/bootstrap/alert.js',
      'bower_components/bootstrap-sass-official/assets/javascripts/bootstrap/button.js',
      'bower_components/bootstrap-sass-official/assets/javascripts/bootstrap/carousel.js',
      'bower_components/bootstrap-sass-official/assets/javascripts/bootstrap/collapse.js',
      'bower_components/bootstrap-sass-official/assets/javascripts/bootstrap/dropdown.js',
      'bower_components/bootstrap-sass-official/assets/javascripts/bootstrap/modal.js',
      'bower_components/bootstrap-sass-official/assets/javascripts/bootstrap/tooltip.js',
      'bower_components/bootstrap-sass-official/assets/javascripts/bootstrap/popover.js',
      'bower_components/bootstrap-sass-official/assets/javascripts/bootstrap/scrollspy.js',
      'bower_components/bootstrap-sass-official/assets/javascripts/bootstrap/tab.js',
      'bower_components/bootstrap-sass-official/assets/javascripts/bootstrap/transition.js'
    ])
    .pipe($.concat('vendors.min.js'))
    .pipe($.uglify())
    .pipe(gulp.dest('build/js'));

  /**
   * FONTS SOURCES
   * Important to add the bootstrap fonts to avoid issues with the fonts include path
   */
  gulp.src([
      'bower_components/bootstrap-sass-official/assets/fonts/bootstrap/*',
      'assets/fonts/*'
    ])
    .pipe(gulp.dest('build/fonts'));

  /**
   * POLYFILLS SOURCES
   * Various polyfills required for old IE
   */
  gulp.src([
      'bower_components/html5shiv/dist/html5shiv.js',
      'bower_components/respond/dest/respond.src.js'
    ])
    .pipe($.concat('polyfills.min.js'))
    .pipe($.uglify())
    .pipe(gulp.dest('build/js'));
});

/**
 * Build styles from SCSS files
 * With error reporting on compiling (so that there's no crash)
 */
gulp.task('styles', function() {
  if (argv.production) { console.log('[styles] Processing styles for production env.' ); }
  else { console.log('[styles] Processing styles for dev env. No minifying here, for sourcemaps!') }
  return gulp.src('assets/sass/main.scss')
    .pipe($.rubySass({style: 'compact', 'sourcemap=none': true}))
      .on('error', $.notify.onError(function (error) {
         console.log(error.message);
         if (!argv.production) {
           return 'Message to the notifier: ' + error.message;
         }
      }))
    .pipe($.if(!argv.production, $.sourcemaps.init()))
    .pipe($.autoprefixer({
      browsers: ['last 2 versions', 'safari 5', 'ie 8', 'ie 9', 'ff 27', 'opera 12.1'],
      cascade: false
    }))
    .pipe($.if(argv.production, $.minifyCss()))
    .pipe($.if(!argv.production, $.sourcemaps.write('.')))
    .pipe(gulp.dest('build/css'));
});

/**
 * Build JS
 * With error reporting on compiling (so that there's no crash)
 * And jshint check to highlight errors as we go.
 */
gulp.task('scripts', function() {
  return gulp.src('assets/js/*.js')
    .pipe($.jshint())
    .pipe($.jshint.reporter('jshint-stylish'))
    .pipe($.concat('main.js'))
    .pipe(gulp.dest('build/js'))
    .pipe($.rename({ suffix: '.min' }))
    .pipe($.uglify())
    .pipe(gulp.dest('build/js'));
});

/**
 * Build Hologram Styleguide
 */
gulp.task('styleguide', function () {
  return gulp.src('hologram_config.yml')
    .pipe($.hologram());
});

<% if (useTwig) { %>
/**
 * Compile TWIG example pages
 */

gulp.task('twig', function () {
    return gulp.src('assets/pages/*.twig')
        .pipe($.twig())
        .pipe(gulp.dest('styleguide/pages'));
});
<% } %>

/**
 * Clean output directories
 */
gulp.task('clean', del.bind(null, ['build', 'styleguide']));

/**
 * Serve
 */
gulp.task('serve', ['styles', 'scripts'<% if (useTwig) { %>, 'twig'<% } %>], function () {
  browserSync({
    server: {
      baseDir: ['styleguide'],
    },
    open: false
  });
  gulp.watch(['styleguide/*.html'], reload);
  gulp.watch(['assets/sass/**/*.scss'], function() {
    runSequence('styles', 'styleguide', reload);
  });
  gulp.watch(['assets/js/**/*.js'], function() {
    runSequence('scripts', reload);
  });
  <% if (useTwig) { %>
  gulp.watch(['assets/pages/**/*'], function() {
    // clean folder before compiling
    del.bind(null, ['styleguide/pages'])
    runSequence('twig', reload);
  });
  <% } %>
});

/**
 * Deploy to GH pages
 */

gulp.task('deploy', function () {
  gulp.src("styleguide/**/*")
    .pipe($.ghPages());
});

/**
 * Task to build assets on production server
 */
gulp.task('build',['clean'], function() {
    argv.production = true;
    runSequence('vendors', 'styles', 'scripts');
});

/**
 * Default task
 */
gulp.task('default', ['clean'], function(cb) {
  runSequence('vendors', 'styles', 'scripts',<% if (useTwig) { %>'twig',<% } %> 'styleguide', cb);
});

<|endoftext|>


<commit_before>/*!
 * dragtable - jquery ui widget to re-order table columns
 * version 3.0
 *
 * Copyright (c) 2010, Jesse Baird <jebaird@gmail.com>
 * 12/2/2010
 * https://github.com/jebaird/dragtable
 *
 * Dual licensed under the MIT (MIT-LICENSE.txt)
 * and GPL (GPL-LICENSE.txt) licenses.
 *
 *
 *
 * Forked from https://github.com/akottr/dragtable - Andres Koetter akottr@gmail.com
 *
 *
 *
 * depends on ui draggable
 *
 *
 * quick down and and dirty on how this works
 * ###########################################
 * so when a column is selected we grab all of the cells in that row and clone append them to a semi copy of the parent table and the
 * "real" cells get a place holder class witch is removed when the dragstop event is triggered
 *
 * TODO:
 * add / create / edit css framework
 * add drag handles
 * click event to handle drag
 * ignore class
 *
 *
 * clean up the api - event driven like ui autocompleate
 * make it easy to have a button swap colums
 *
 */

(function($) {
  $.widget("jb.dragtable", {
  		//TODO: implement this
  		eventWidgetPrefix: 'dragtable',

		options: {
			//used to the col headers, data containted in here is used to set / get the name of the col
			dataHeader:'data-header',
			//handle
			handle:'.dragtable-drag-handle',
			//optional call back used when the col order has changed
			change: $.noop,

			//called after we create the drag display allows user to customize the look of the col in drag
			displayHelper: $.noop,

			//TODO: Faze these out
			// when a col is dragged use this to find the symantic elements, for speed
			tableElemIndex:{
				head: '0',
				body: '1',
				foot: '2'
			}
		},

		_create: function() {

			//console.log(this);
			//used start/end of drag
			this.startIndex = null;
			this.endIndex = null;
			this.currentColumnCollection = [];//the refferences to the table cells that are getting dragged

			//used on drag event to detect what direction the mouse is moving
			//init on drag start
			this.prevMouseX = 0;

			var self = this,
			o = self.options,
			el = self.element;

			el.delegate('thead th:not( :has(' + o.handle + ')), ' + o.handle,'mousedown',function(e){
				var $this = $(this),
				$dragDisplay = self.getCol($this.index());
				//console.log(dragDisplay)
				self._eventHelper('displayHelper',{},{
					'draggable':$dragDisplay
				});

				$dragDisplay
				.focus()
				.appendTo(document.body)
				.select(function(){
					return false;
				})
				.css({
					position:'absolute'
				})
				.position({
					of:this,
					my:'center bottom',
					at:'center bottom'

				})
				.draggable({

					handle: 'th',
					axis: 'x',
					containment: self.element,
					start: function(e,ui){
						self.prevMouseX = e.pageX;
						//TODO: trigger widget option here

					},
					drag: function(e, ui){
						//console.log(e);

						var columnPos = self._findElementPosition(self.currentColumnCollection[0]),
						half = self.currentColumnCollection[0].clientWidth / 2;
						//move left
						if(e.pageX < self.prevMouseX){
							var threshold = columnPos.x - half;
							if(ui.position.left < threshold){
								//console.info('move left');
								self._swapCol(self.startIndex-1);
								self._eventHelper('change',e);

							}

						}else{
							var threshold = columnPos.x + half;
							if(ui.position.left > threshold){
								//console.info('move right');
								self._swapCol(self.startIndex+1);
								self._eventHelper('change',e);

							}
						}
						//update mouse position
						self.prevMouseX = e.pageX;

					},
					stop: function(e, ui){

						self._dropCol($dragDisplay);
						self.prevMouseX = 0;
					}
				})

				//############
			});

		},

		_setOption: function(option, value) {
			$.Widget.prototype._setOption.apply( this, arguments );

		},

		/*
		 * get the selected index cell out of table row
		 * works dam fast
		 */
		_getCells: function( elem, index ){
			var ei = this.options.tableElemIndex,

			//TODO: clean up this format
			tds = {
				'semantic':{
					'0': [],//head throws error if ei.head or ei['head']
					'1': [],//body
					'2': []//footer
				},
				'array':[]
			};

			//console.log(index);
			//check does this col exsist
			if(index <= -1 || typeof elem.rows[0].cells[index] == 'undefined'){
				return tds;
			}

			for(var i = 0, length = elem.rows.length; i < length; i++){
				var td = elem.rows[i].cells[index];

				tds.array.push(td);

				switch(td.parentNode.parentNode.nodeName){
					case 'THEAD':
					case 'thead':
						tds.semantic[ei.head].push(td);
						break;
					case 'TFOOT':
					case 'tfoot':
						tds.semantic[ei.foot].push(td);
						break;
					default:
						tds.semantic[ei.body].push(td);
						break;
				}

		 	}

		 	return tds;
		},
		/*
		 * return and array of children excluding text nodes
		 * used only on this.element
		 */
		_getChildren: function(){

			var children = this.element[0].childNodes,
			ret = [];
			for(var i = 0, length = children.length; i < length; i++){
				var e = children[i];
				if(e.nodeType == 1){
					ret.push(e);
				}
			}

			return ret;
		},

		_getElementAttributes: function(element){

        	var attrsString = '',
	        attrs = element.attributes;
	        for(var i=0, length = attrs.length; i < length; i++) {
	            attrsString += attrs[i].nodeName + '="' + attrs[i].nodeValue+'"';
	        }
	        return attrsString;
		},
		/*
		 * currently not uses
		 */
		_swapNodes: function(a, b) {
        	var aparent = a.parentNode,
        	asibling = a.nextSibling === b ? a : a.nextSibling;
        	b.parentNode.insertBefore(a, b);
        	aparent.insertBefore(b, asibling);
     	},
     	/*
     	 * faster than swap nodes
     	 * only works if a b parent are the same, works great for colums
     	 */
     	_swapCells: function(a, b) {
        	a.parentNode.insertBefore(b, a);
     	},
     	/*
     	 * use this instead of jquerys offset, in the cases were using is faster than creating a jquery collection
     	 */
		_findElementPosition: function( oElement ) {
			if( typeof( oElement.offsetParent ) != 'undefined' ) {
				for( var posX = 0, posY = 0; oElement; oElement = oElement.offsetParent ) {
					posX += oElement.offsetLeft;
					posY += oElement.offsetTop;
				}
				return {'x':posX, 'y':posY };
			} else {
				return {'x':oElement.x, 'y':oElement.y };
			}
		},
		/*
		 * used to tirgger optional events
		 */
		_eventHelper: function(eventName ,eventObj, additionalData){
			this._trigger(
				eventName,
				eventObj,
				$.extend({
					column: this.currentColumnCollection,
					order: this.order()
				},additionalData)
			);
		},
		/*
		 * build copy of table and attach the selected col to it, also removes the select col out of the table
		 * @returns copy of table with the selected col
		 */
		getCol: function(index){
			//console.log('index of col '+index);
			//drag display is just simple html
			//console.profile('selectCol');

			//colHeader.addClass('ui-state-disabled')

			var $table = this.element,
			self = this,
			eIndex = self.options.tableElemIndex,
			//BUG: IE thinks that this table is disabled, dont know how that happend
			$dragDisplay = $('

')
			.addClass('dragtable-drag-col');

			//start and end are the same to start out with
			self.startIndex = self.endIndex = index;

		 	var cells = self._getCells($table[0], index);
			self.currentColumnCollection = cells.array;
			//console.log(cells);
			//################################

			//TODO: convert to for in // its faster than each
			$.each(cells.semantic,function(k,collection){
				//dont bother processing if there is nothing here

				if(collection.length == 0){
					return;
				}
				switch(k){
					case '0':

						var target = document.createElement('thead');
						$dragDisplay[0].appendChild(target);
						//
						// var target = $('

')
						// .appendTo($dragDisplay);

						break;
					case '2':
						break;
					default:
						var target = document.createElement('tbody');
						$dragDisplay[0].appendChild(target);
						// var target = $('

')
						// .appendTo($dragDisplay);

						break;
				}

				for(var i = 0,length = collection.length; i < length; i++){

					var clone = collection[i].cloneNode(true);
					collection[i].className+=' dragtable-col-placeholder';
					var tr = document.createElement('tr');
					tr.appendChild(clone);
					//console.log(tr);

					target.appendChild(tr);
					//collection[i]=;
				}
			});
		// console.log($dragDisplay);
		//console.profileEnd('selectCol')
		$dragDisplay  = $('

').append($dragDisplay)
		 return $dragDisplay;
		},

		/*
		 * move column left or right
		 */
		_swapCol: function( to ){

			//cant swap if same postion
			if(to == this.startIndex){
				return false;
			}

			from = this.startIndex;
			this.endIndex = to;

	        if(from < to) {
	        	//console.log('move right');
	        	for(var i = from; i < to; i++) {
	        		var row2 = this._getCells(this.element[0],i+1);
	        	//	console.log(row2)
	        		for(var j = 0, length = row2.array.length; j < length; j++){
	          			this._swapCells(this.currentColumnCollection[j],row2.array[j]);
	          		}
	          	}
	        } else {
	        	//console.log('move left');
	        	for(var i = from; i > to; i--) {
	            	var row2 = this._getCells(this.element[0],i-1);
	            	for(var j = 0, length = row2.array.length; j < length; j++){
	          			this._swapCells(row2.array[j],this.currentColumnCollection[j]);
	          		}
	        	}
	        }

	        this.startIndex = this.endIndex;
		},
		/*
		 * called when drag start is finished
		 */
		_dropCol: function($dragDisplay){
		//	console.profile('dropCol');
			var self = this;

			if($dragDisplay){
				$dragDisplay.remove();
			}
			//remove placeholder class
			for(var i = 0, length = self.currentColumnCollection.length; i < length; i++){
				var td = self.currentColumnCollection[i];

				td.className = td.className.replace(' dragtable-col-placeholder','');
			}

		},
		/*
		 * get / set the current order of the cols
		 */
		order: function(order){
			var self = this,
			elem = self.element,
			options = self.options,
			headers = elem.find('thead tr:first').children('th');

			if(order == undefined){
				//get
				var ret = [];
				headers.each(function(){
					var header = this.getAttribute(options.dataHeader);
					if(header == null){
						//the attr is missing so grab the text and use that
						header = $(this).text();
					}

					ret.push(header);

				});

				return ret;

			}else{
				//set
				//headers and order have to match up
				if(order.length != headers.length){
					//console.log('length not the same')
					return self;
				}
				for(var i = 0, length = order.length; i < length; i++){

					 var start = headers.filter('['+ options.dataHeader +'='+ order[i] +']').index();
					 if(start != -1){
					 	//console.log('start index '+start+' - swap to '+i);
					 	self.startIndex = start;

						self.currentColumnCollection = self._getCells(self.element[0], start).array;

					 	self._swapCol(i);
					 }

				}
				self._eventHelper('change',{});
				return self;
			}
		},

		destroy: function() {
			return this;
		}

	});

})(jQuery);<commit_msg>fixed drag after col was dragged with handle<commit_after>/*!
 * dragtable - jquery ui widget to re-order table columns
 * version 3.0
 *
 * Copyright (c) 2010, Jesse Baird <jebaird@gmail.com>
 * 12/2/2010
 * https://github.com/jebaird/dragtable
 *
 * Dual licensed under the MIT (MIT-LICENSE.txt)
 * and GPL (GPL-LICENSE.txt) licenses.
 *
 *
 *
 * Forked from https://github.com/akottr/dragtable - Andres Koetter akottr@gmail.com
 *
 *
 *
 * depends on ui draggable
 *
 *
 * quick down and and dirty on how this works
 * ###########################################
 * so when a column is selected we grab all of the cells in that row and clone append them to a semi copy of the parent table and the
 * "real" cells get a place holder class witch is removed when the dragstop event is triggered
 *
 * TODO:
 * add / create / edit css framework
 * add drag handles
 * click event to handle drag
 * ignore class
 *
 *
 * clean up the api - event driven like ui autocompleate
 * make it easy to have a button swap colums
 *
 */

(function($) {
  $.widget("jb.dragtable", {
  		//TODO: implement this
  		eventWidgetPrefix: 'dragtable',

		options: {
			//used to the col headers, data containted in here is used to set / get the name of the col
			dataHeader:'data-header',
			//handle
			handle:'.dragtable-drag-handle',
			//optional call back used when the col order has changed
			change: $.noop,

			//called after we create the drag display allows user to customize the look of the col in drag
			displayHelper: $.noop,

			//TODO: Faze these out
			// when a col is dragged use this to find the symantic elements, for speed
			tableElemIndex:{
				head: '0',
				body: '1',
				foot: '2'
			}
		},

		_create: function() {

			//console.log(this);
			//used start/end of drag
			this.startIndex = null;
			this.endIndex = null;
			this.currentColumnCollection = [];//the refferences to the table cells that are getting dragged

			//used on drag event to detect what direction the mouse is moving
			//init on drag start
			this.prevMouseX = 0;

			var self = this,
			o = self.options,
			el = self.element;

			//grab the ths and the handles and bind them
			el.delegate('thead th:not( :has(' + o.handle + ')), ' + o.handle,'mousedown',function(e){
				var $handle = $(this);
				//make sure we are working with a th instead of a handle
				if($handle.hasClass(o.handle.replace('.',''))){
					$handle = $handle.closest('th');
				}

				var $dragDisplay = self.getCol($handle.index());
				//console.log(dragDisplay)
				self._eventHelper('displayHelper',{},{
					'draggable':$dragDisplay
				});

				$dragDisplay
				.focus()
				.appendTo(document.body)
				.select(function(){
					return false;
				})
				.css({
					position:'absolute'
				})
				.position({
					of:this,
					my:'center bottom',
					at:'center bottom'

				})
				.draggable({
					handle:'thead th:not( :has(' + o.handle + ')), ' + o.handle,
					//handle: 'th',
					axis: 'x',
					containment: self.element,
					start: function(e,ui){
						self.prevMouseX = e.pageX;
						//TODO: trigger widget option here

					},
					drag: function(e, ui){
						//console.log(e);

						var columnPos = self._findElementPosition(self.currentColumnCollection[0]),
						half = self.currentColumnCollection[0].clientWidth / 2;
						//move left
						if(e.pageX < self.prevMouseX){
							var threshold = columnPos.x - half;
							if(ui.position.left < threshold){
								//console.info('move left');
								self._swapCol(self.startIndex-1);
								self._eventHelper('change',e);

							}

						}else{
							var threshold = columnPos.x + half;
							if(ui.position.left > threshold){
								//console.info('move right');
								self._swapCol(self.startIndex+1);
								self._eventHelper('change',e);

							}
						}
						//update mouse position
						self.prevMouseX = e.pageX;

					},
					stop: function(e, ui){

						self._dropCol($dragDisplay);
						self.prevMouseX = 0;
					}
				})

				//############
			});

		},

		_setOption: function(option, value) {
			$.Widget.prototype._setOption.apply( this, arguments );

		},

		/*
		 * get the selected index cell out of table row
		 * works dam fast
		 */
		_getCells: function( elem, index ){
			var ei = this.options.tableElemIndex,

			//TODO: clean up this format
			tds = {
				'semantic':{
					'0': [],//head throws error if ei.head or ei['head']
					'1': [],//body
					'2': []//footer
				},
				'array':[]
			};

			//console.log(index);
			//check does this col exsist
			if(index <= -1 || typeof elem.rows[0].cells[index] == 'undefined'){
				return tds;
			}

			for(var i = 0, length = elem.rows.length; i < length; i++){
				var td = elem.rows[i].cells[index];

				tds.array.push(td);

				switch(td.parentNode.parentNode.nodeName){
					case 'THEAD':
					case 'thead':
						tds.semantic[ei.head].push(td);
						break;
					case 'TFOOT':
					case 'tfoot':
						tds.semantic[ei.foot].push(td);
						break;
					default:
						tds.semantic[ei.body].push(td);
						break;
				}

		 	}

		 	return tds;
		},
		/*
		 * return and array of children excluding text nodes
		 * used only on this.element
		 */
		_getChildren: function(){

			var children = this.element[0].childNodes,
			ret = [];
			for(var i = 0, length = children.length; i < length; i++){
				var e = children[i];
				if(e.nodeType == 1){
					ret.push(e);
				}
			}

			return ret;
		},

		_getElementAttributes: function(element){

        	var attrsString = '',
	        attrs = element.attributes;
	        for(var i=0, length = attrs.length; i < length; i++) {
	            attrsString += attrs[i].nodeName + '="' + attrs[i].nodeValue+'"';
	        }
	        return attrsString;
		},
		/*
		 * currently not uses
		 */
		_swapNodes: function(a, b) {
        	var aparent = a.parentNode,
        	asibling = a.nextSibling === b ? a : a.nextSibling;
        	b.parentNode.insertBefore(a, b);
        	aparent.insertBefore(b, asibling);
     	},
     	/*
     	 * faster than swap nodes
     	 * only works if a b parent are the same, works great for colums
     	 */
     	_swapCells: function(a, b) {
        	a.parentNode.insertBefore(b, a);
     	},
     	/*
     	 * use this instead of jquerys offset, in the cases were using is faster than creating a jquery collection
     	 */
		_findElementPosition: function( oElement ) {
			if( typeof( oElement.offsetParent ) != 'undefined' ) {
				for( var posX = 0, posY = 0; oElement; oElement = oElement.offsetParent ) {
					posX += oElement.offsetLeft;
					posY += oElement.offsetTop;
				}
				return {'x':posX, 'y':posY };
			} else {
				return {'x':oElement.x, 'y':oElement.y };
			}
		},
		/*
		 * used to tirgger optional events
		 */
		_eventHelper: function(eventName ,eventObj, additionalData){
			this._trigger(
				eventName,
				eventObj,
				$.extend({
					column: this.currentColumnCollection,
					order: this.order()
				},additionalData)
			);
		},
		/*
		 * build copy of table and attach the selected col to it, also removes the select col out of the table
		 * @returns copy of table with the selected col
		 */
		getCol: function(index){
			//console.log('index of col '+index);
			//drag display is just simple html
			//console.profile('selectCol');

			//colHeader.addClass('ui-state-disabled')

			var $table = this.element,
			self = this,
			eIndex = self.options.tableElemIndex,
			//BUG: IE thinks that this table is disabled, dont know how that happend
			$dragDisplay = $('

')
			.addClass('dragtable-drag-col');

			//start and end are the same to start out with
			self.startIndex = self.endIndex = index;

		 	var cells = self._getCells($table[0], index);
			self.currentColumnCollection = cells.array;
			//console.log(cells);
			//################################

			//TODO: convert to for in // its faster than each
			$.each(cells.semantic,function(k,collection){
				//dont bother processing if there is nothing here

				if(collection.length == 0){
					return;
				}
				switch(k){
					case '0':

						var target = document.createElement('thead');
						$dragDisplay[0].appendChild(target);
						//
						// var target = $('

')
						// .appendTo($dragDisplay);

						break;
					case '2':
						break;
					default:
						var target = document.createElement('tbody');
						$dragDisplay[0].appendChild(target);
						// var target = $('

')
						// .appendTo($dragDisplay);

						break;
				}

				for(var i = 0,length = collection.length; i < length; i++){

					var clone = collection[i].cloneNode(true);
					collection[i].className+=' dragtable-col-placeholder';
					var tr = document.createElement('tr');
					tr.appendChild(clone);
					//console.log(tr);

					target.appendChild(tr);
					//collection[i]=;
				}
			});
		// console.log($dragDisplay);
		//console.profileEnd('selectCol')
		$dragDisplay  = $('

').append($dragDisplay)
		 return $dragDisplay;
		},

		/*
		 * move column left or right
		 */
		_swapCol: function( to ){

			//cant swap if same postion
			if(to == this.startIndex){
				return false;
			}

			from = this.startIndex;
			this.endIndex = to;

	        if(from < to) {
	        	//console.log('move right');
	        	for(var i = from; i < to; i++) {
	        		var row2 = this._getCells(this.element[0],i+1);
	        	//	console.log(row2)
	        		for(var j = 0, length = row2.array.length; j < length; j++){
	          			this._swapCells(this.currentColumnCollection[j],row2.array[j]);
	          		}
	          	}
	        } else {
	        	//console.log('move left');
	        	for(var i = from; i > to; i--) {
	            	var row2 = this._getCells(this.element[0],i-1);
	            	for(var j = 0, length = row2.array.length; j < length; j++){
	          			this._swapCells(row2.array[j],this.currentColumnCollection[j]);
	          		}
	        	}
	        }

	        this.startIndex = this.endIndex;
		},
		/*
		 * called when drag start is finished
		 */
		_dropCol: function($dragDisplay){
		//	console.profile('dropCol');
			var self = this;

			if($dragDisplay){
				$dragDisplay.remove();
			}
			//remove placeholder class
			for(var i = 0, length = self.currentColumnCollection.length; i < length; i++){
				var td = self.currentColumnCollection[i];

				td.className = td.className.replace(' dragtable-col-placeholder','');
			}

		},
		/*
		 * get / set the current order of the cols
		 */
		order: function(order){
			var self = this,
			elem = self.element,
			options = self.options,
			headers = elem.find('thead tr:first').children('th');

			if(order == undefined){
				//get
				var ret = [];
				headers.each(function(){
					var header = this.getAttribute(options.dataHeader);
					if(header == null){
						//the attr is missing so grab the text and use that
						header = $(this).text();
					}

					ret.push(header);

				});

				return ret;

			}else{
				//set
				//headers and order have to match up
				if(order.length != headers.length){
					//console.log('length not the same')
					return self;
				}
				for(var i = 0, length = order.length; i < length; i++){

					 var start = headers.filter('['+ options.dataHeader +'='+ order[i] +']').index();
					 if(start != -1){
					 	//console.log('start index '+start+' - swap to '+i);
					 	self.startIndex = start;

						self.currentColumnCollection = self._getCells(self.element[0], start).array;

					 	self._swapCol(i);
					 }

				}
				self._eventHelper('change',{});
				return self;
			}
		},

		destroy: function() {
			return this;
		}

	});

})(jQuery);<|endoftext|>


<commit_before>/*
 * Copyright (C) 2017 - present Instructure, Inc.
 *
 * This file is part of Canvas.
 *
 * Canvas is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License as published by the Free
 * Software Foundation, version 3 of the License.
 *
 * Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Affero General Public License along
 * with this program. If not, see <http://www.gnu.org/licenses/>.
 */

import React from 'react'
import {arrayOf, shape, string} from 'prop-types'
import Select from '@instructure/ui-core/lib/components/Select'
import I18n from 'i18n!edit_timezone'

export default function TimeZoneSelect({label, timezones, priority_zones, ...otherPropsToPassOnToSelect}) {
  return (
    <Select {...otherPropsToPassOnToSelect} label={label} >
      <option value="" />
      {[
        {label: I18n.t('Common Timezones'), timezones: priority_zones},
        {label: I18n.t('All Timezones'), timezones}
      ].map(({label, timezones}) => (
        <optgroup key={label} label={label}>
          {timezones.map(zone => (
            <option key={zone.name} value={zone.name}>
              {zone.localized_name}
            </option>
          ))}
        </optgroup>
      ))}
    </Select>
  )
}

const timezoneShape = shape({
  name: string.isRequired,
  localized_name: string.isRequired
}).isRequired

TimeZoneSelect.propTypes = {
  ...Select.propTypes, // this accepts any prop you'd pass to InstUI's Select. see it's docs for examples
  timezones: arrayOf(timezoneShape),
  priority_zones: arrayOf(timezoneShape)
}

TimeZoneSelect.defaultProps = {
  // TODO: change ENV.LOCALE to process.env.BUILD_LOCALE once we do locale-specific builds so we only pull in that one json file
  ...require(`./localized-timezone-lists/${ENV.LOCALE || 'en'}.json`),
  label: I18n.t('Time Zone')
}
<commit_msg>Fall back to English if user has bogus locale set<commit_after>/*
 * Copyright (C) 2017 - present Instructure, Inc.
 *
 * This file is part of Canvas.
 *
 * Canvas is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License as published by the Free
 * Software Foundation, version 3 of the License.
 *
 * Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Affero General Public License along
 * with this program. If not, see <http://www.gnu.org/licenses/>.
 */

import React from 'react'
import {arrayOf, shape, string} from 'prop-types'
import Select from '@instructure/ui-core/lib/components/Select'
import I18n from 'i18n!edit_timezone'

export default function TimeZoneSelect({label, timezones, priority_zones, ...otherPropsToPassOnToSelect}) {
  return (
    <Select {...otherPropsToPassOnToSelect} label={label} >
      <option value="" />
      {[
        {label: I18n.t('Common Timezones'), timezones: priority_zones},
        {label: I18n.t('All Timezones'), timezones}
      ].map(({label, timezones}) => (
        <optgroup key={label} label={label}>
          {timezones.map(zone => (
            <option key={zone.name} value={zone.name}>
              {zone.localized_name}
            </option>
          ))}
        </optgroup>
      ))}
    </Select>
  )
}

const timezoneShape = shape({
  name: string.isRequired,
  localized_name: string.isRequired
}).isRequired

TimeZoneSelect.propTypes = {
  ...Select.propTypes, // this accepts any prop you'd pass to InstUI's Select. see it's docs for examples
  timezones: arrayOf(timezoneShape),
  priority_zones: arrayOf(timezoneShape)
}

let defaultsJSON
try {
  defaultsJSON = require(`./localized-timezone-lists/${ENV.LOCALE || 'en'}.json`)
} catch (e) {
  // fall back to english if a user has a locale set that we don't have a list for
  defaultsJSON = require(`./localized-timezone-lists/en.json`)
}

TimeZoneSelect.defaultProps = {
  // TODO: change ENV.LOCALE to process.env.BUILD_LOCALE once we do locale-specific builds so we only pull in that one json file
  ...defaultsJSON,
  label: I18n.t('Time Zone')
}
<|endoftext|>


<commit_before>// Copyright 2019-2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * This sample demonstrates how to perform basic operations on topics with
 * the Google Cloud Pub/Sub API.
 *
 * For more information, see the README.md under /pubsub and the documentation
 * at https://cloud.google.com/pubsub/docs.
 */

'use strict';

// sample-metadata:
//   title: Publish Message
//   description: Publishes a message to a topic.
//   usage: node publishMessage.js <topic-name> <data>

function main(
  topicName = 'YOUR_TOPIC_NAME',
  data = JSON.stringify({foo: 'bar'})
) {
  // [START pubsub_publish]
  // [START pubsub_quickstart_publisher]
  /**
   * TODO(developer): Uncomment these variables before running the sample.
   */
  // const topicName = 'YOUR_TOPIC_NAME';
  // const data = JSON.stringify({foo: 'bar'});

  // Imports the Google Cloud client library
  const {PubSub} = require('@google-cloud/pubsub');

  // Creates a client; cache this for further use
  const pubSubClient = new PubSub();

  async function publishMessage() {
    /**
     * TODO(developer): Uncomment the following lines to run the sample.
     */
    // const topicName = 'my-topic';

    // Publishes the message as a string, e.g. "Hello, world!" or JSON.stringify(someObject)
    const dataBuffer = Buffer.from(data);

    const messageId = await pubSubClient.topic(topicName).publish(dataBuffer);
    console.log(`Message ${messageId} published.`);
  }

  publishMessage().catch(console.error);
  // [END pubsub_publish]
  // [END pubsub_quickstart_publisher]
}

main(...process.argv.slice(2));
<commit_msg>docs(sample): remove repeated code #1098<commit_after>// Copyright 2019-2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * This sample demonstrates how to perform basic operations on topics with
 * the Google Cloud Pub/Sub API.
 *
 * For more information, see the README.md under /pubsub and the documentation
 * at https://cloud.google.com/pubsub/docs.
 */

'use strict';

// sample-metadata:
//   title: Publish Message
//   description: Publishes a message to a topic.
//   usage: node publishMessage.js <topic-name> <data>

function main(
  topicName = 'YOUR_TOPIC_NAME',
  data = JSON.stringify({foo: 'bar'})
) {
  // [START pubsub_publish]
  // [START pubsub_quickstart_publisher]
  /**
   * TODO(developer): Uncomment these variables before running the sample.
   */
  // const topicName = 'YOUR_TOPIC_NAME';
  // const data = JSON.stringify({foo: 'bar'});

  // Imports the Google Cloud client library
  const {PubSub} = require('@google-cloud/pubsub');

  // Creates a client; cache this for further use
  const pubSubClient = new PubSub();

  async function publishMessage() {
    // Publishes the message as a string, e.g. "Hello, world!" or JSON.stringify(someObject)
    const dataBuffer = Buffer.from(data);

    const messageId = await pubSubClient.topic(topicName).publish(dataBuffer);
    console.log(`Message ${messageId} published.`);
  }

  publishMessage().catch(console.error);
  // [END pubsub_publish]
  // [END pubsub_quickstart_publisher]
}

main(...process.argv.slice(2));
<|endoftext|>


<commit_before>/*global -$ */
'use strict';
// generated on <%= (new Date).toISOString().split('T')[0] %> using <%= pkg.name %> <%= pkg.version %>
var gulp = require('gulp');
var $ = require('gulp-load-plugins')();
var browserSync = require('browser-sync');
var reload = browserSync.reload;

gulp.task('clean', require('del').bind(null, ['dist']));

gulp.task('templates', function () {
  gulp.src('app/**/*.jade')
    .pipe($.jade({pretty: true}))
    .pipe(gulp.dest('dist'));
});

gulp.task('templatesMin', ['styles', 'scripts'], function () {
  var assets = $.useref.assets({searchPath: ['.', '.tmp']});

  gulp.src('app/**/*.jade')
    .pipe($.jade({pretty: true}))
    .pipe(assets)
    .pipe($.if('*.js', $.uglify()))
    .pipe($.if('*.css', $.csso()))
    .pipe(assets.restore())
    .pipe($.useref())
    .pipe($.if('*.html', $.minifyHtml({conditionals: true})))
    .pipe(gulp.dest('dist'));
});

gulp.task('styles', function () {
  gulp.src('app/styles/**/*.styl')
    .pipe($.stylus({
      compress: true
    }))
    .pipe($.concat('main.css'))
    .pipe($.postcss([
      require('autoprefixer-core')({ browsers: ['> 2%']})
    ]))
    .pipe(gulp.dest('dist/styles'));
});

gulp.task('scripts', function () {
  gulp.src('app/scripts/**/*.coffee')
    .pipe($.coffee())
    .pipe($.uglify())
    .pipe(gulp.dest('.tmp/scripts'))
});

gulp.task('fonts', function () {
  gulp.src(require('main-bower-files')().concat('app/fonts/**/*'))
    .pipe($.filter('**/*.{eot,svg,ttf,woff}'))
    .pipe($.flatten())
    .pipe(gulp.dest('dist/fonts'));
});

gulp.task('images', function () {
  gulp.src('app/images/**/*')
    .pipe($.imagemin({
      optimizationLevel: 3,
      progressive: true,
      interlaced: true
    }))
    .pipe(gulp.dest('dist/images'));
});

gulp.task('extras', function () {
  gulp.src([
    'app/*.*',
    '!app/*.jade'
  ], {
    dot: true
  }).pipe(gulp.dest('dist'));
});

gulp.task('build', ['clean'], function () {
  gulp.start(['templatesMin', 'fonts', 'images', 'extras']);

  gulp.src('dist/**/*').pipe($.size({title: 'build', gzip: true}));
});

gulp.task('wiredep', function () {
  var wiredep = require('wiredep').stream;

  gulp.src('app/*.jade')
    .pipe(wiredep({
      ignorePath: /^(\.\.\/)*\.\./
    }))
    .pipe(gulp.dest('app'));
});

gulp.task('default', ['clean'], function () {
  gulp.start('build');
});

gulp.task('serve:develop', ['clean', 'templates', 'fonts', 'styles', 'scripts'], function () {
  browserSync({
    notify: false,
    port: 9000,
    server: {
      baseDir: ['dist', '.tmp', 'app'],
      routes: {
        '/bower_components': 'bower_components'
      }
    }
  });

  gulp.watch([
    'dist/*.html',
    'dist/styles/**/*.css',
    'dist/scripts/**/*.js',
    'dist/fonts/**/*',
    'app/images/**/*'
  ]).on('change', reload);

  gulp.watch('app/scripts/**/*.coffee', ['scripts', reload]);
  gulp.watch('app/styles/**/*.styl', ['styles', reload]);
  gulp.watch('bower.json', ['wiredep', reload]);
});

gulp.task('serve:production', ['build'], function () {
  browserSync({
    notify: false,
    port: 9000,
    server: {
      baseDir: ['dist']
    }
  });

  gulp.watch([
    'dist/*.html',
    'dist/styles/**/*.css',
    'dist/scripts/**/*.js',
    'dist/fonts/**/*',
    'dist/images/**/*'
  ]).on('change', reload);
});

gulp.task('serve', ['serve:develop'], function () {
});
<commit_msg>Watch templates in serve:develop<commit_after>/*global -$ */
'use strict';
// generated on <%= (new Date).toISOString().split('T')[0] %> using <%= pkg.name %> <%= pkg.version %>
var gulp = require('gulp');
var $ = require('gulp-load-plugins')();
var browserSync = require('browser-sync');
var reload = browserSync.reload;

gulp.task('clean', require('del').bind(null, ['dist']));

gulp.task('templates', function () {
  gulp.src('app/**/*.jade')
    .pipe($.jade({pretty: true}))
    .pipe(gulp.dest('dist'));
});

gulp.task('templatesMin', ['styles', 'scripts'], function () {
  var assets = $.useref.assets({searchPath: ['.', '.tmp']});

  gulp.src('app/**/*.jade')
    .pipe($.jade({pretty: true}))
    .pipe(assets)
    .pipe($.if('*.js', $.uglify()))
    .pipe($.if('*.css', $.csso()))
    .pipe(assets.restore())
    .pipe($.useref())
    .pipe($.if('*.html', $.minifyHtml({conditionals: true})))
    .pipe(gulp.dest('dist'));
});

gulp.task('styles', function () {
  gulp.src('app/styles/**/*.styl')
    .pipe($.stylus({
      compress: true
    }))
    .pipe($.concat('main.css'))
    .pipe($.postcss([
      require('autoprefixer-core')({ browsers: ['> 2%']})
    ]))
    .pipe(gulp.dest('dist/styles'));
});

gulp.task('scripts', function () {
  gulp.src('app/scripts/**/*.coffee')
    .pipe($.coffee())
    .pipe($.uglify())
    .pipe(gulp.dest('.tmp/scripts'))
});

gulp.task('fonts', function () {
  gulp.src(require('main-bower-files')().concat('app/fonts/**/*'))
    .pipe($.filter('**/*.{eot,svg,ttf,woff}'))
    .pipe($.flatten())
    .pipe(gulp.dest('dist/fonts'));
});

gulp.task('images', function () {
  gulp.src('app/images/**/*')
    .pipe($.imagemin({
      optimizationLevel: 3,
      progressive: true,
      interlaced: true
    }))
    .pipe(gulp.dest('dist/images'));
});

gulp.task('extras', function () {
  gulp.src([
    'app/*.*',
    '!app/*.jade'
  ], {
    dot: true
  }).pipe(gulp.dest('dist'));
});

gulp.task('build', ['clean'], function () {
  gulp.start(['templatesMin', 'fonts', 'images', 'extras']);

  gulp.src('dist/**/*').pipe($.size({title: 'build', gzip: true}));
});

gulp.task('wiredep', function () {
  var wiredep = require('wiredep').stream;

  gulp.src('app/*.jade')
    .pipe(wiredep({
      ignorePath: /^(\.\.\/)*\.\./
    }))
    .pipe(gulp.dest('app'));
});

gulp.task('default', ['clean'], function () {
  gulp.start('build');
});

gulp.task('serve:develop', ['clean'], function () {
  gulp.start(['templates', 'fonts', 'styles', 'scripts']);

  browserSync({
    notify: false,
    port: 9000,
    server: {
      baseDir: ['dist', '.tmp', 'app'],
      routes: {
        '/bower_components': 'bower_components'
      }
    }
  });

  gulp.watch([
    'dist/**/*.html',
    'dist/styles/**/*.css',
    'dist/scripts/**/*.js',
    'dist/fonts/**/*',
    'app/images/**/*'
  ]).on('change', reload);

  gulp.watch('app/**/*.jade', ['templates', reload]);
  gulp.watch('app/scripts/**/*.coffee', ['scripts', reload]);
  gulp.watch('app/styles/**/*.styl', ['styles', reload]);
  gulp.watch('bower.json', ['wiredep', reload]);
});

gulp.task('serve:production', ['build'], function () {
  browserSync({
    notify: false,
    port: 9000,
    server: {
      baseDir: ['dist']
    }
  });

  gulp.watch([
    'dist/*.html',
    'dist/styles/**/*.css',
    'dist/scripts/**/*.js',
    'dist/fonts/**/*',
    'dist/images/**/*'
  ]).on('change', reload);
});

gulp.task('serve', ['serve:develop'], function () {
});
<|endoftext|>