<commit_before>import React, { Component } from 'react';
import {
    Text,
    View,
    ScrollView,
    Image,
    ActivityIndicator,
    AsyncStorage,
    Animated,
    LayoutAnimation,
    UIManager,
    AppState
} from 'react-native';
import { connect } from 'react-redux';
import moment from 'moment';
import Orientation from 'react-native-orientation';
import Swiper from 'react-native-swiper'
import { IconButton } from './common';
import { fetchWeek, selectDay } from '../actions';
import i18n from 'react-native-i18n';
import bundledTranslations from '../translations';
import DayView from './DayViewComponent';
import DaySwitcher from './DaySwitcherComponent';
import WeekView from './WeekViewComponent';

class Timetable extends Component {
    state = {
        headerHeight: new Animated.Value(60),
        iconButtonSize: new Animated.Value(20),
        iconButtonPadding: new Animated.Value(10),
        doneInitialScrollWeekView: false
    };

    componentWillMount() {
        setTimeout(() => { this.props.fetchWeek(this.props.user, this.props.program, '17', this.props.semester); }, 1000);  // Using the 17th week of the year to get results from the API
        // TODO: Try to refactor the line above so one does not need the 'setTimeout()'

        let initialOrientation = Orientation.getInitialOrientation();
        if (initialOrientation === 'PORTRAIT') {
            this.setState({ orientation: initialOrientation });
        } else if (initialOrientation === 'LANDSCAPE') {
            this.setState({ orientation: initialOrientation });
        }

        Orientation.addOrientationListener(this._orientationDidChange);

        AppState.addEventListener('change', this._handleAppStateChange.bind(this));

        let today = moment().format('ddd');
        let hour = moment().format('H');
        if (today === "Sat" || today === "Sun") {  // If it's on a weekend, jump to the next Monday
            this.props.selectDay('Mon');
        } else if (hour >= 19 && today === "Fri") {  // If it's after 19:00 and a Friday, jump to the next Monday
            this.props.selectDay('Mon');
        } else if (hour >= 19 && today !== "Fri") {  // If it's after 19:00, jump to the following day of the week
            today = moment().add(1, 'day').format('ddd');
            this.props.selectDay(today);
        } else {
            this.props.selectDay(today);
        }
    }

    componentWillUpdate() {
        // Had to disable the animation in virtue of it causing the 'DayView' to freeze during the rendering
        // UIManager.setLayoutAnimationEnabledExperimental && UIManager.setLayoutAnimationEnabledExperimental(true);
        // LayoutAnimation.easeInEaseOut();

        if (this.state.oldScrollOffsetY > 90) {
            Animated.spring(
                this.state.headerHeight,
                {
                    toValue: 35,
                    friction: 10,
                    tension: 100
                }
            ).start();
            Animated.spring(
                this.state.iconButtonSize,
                {
                    toValue: 10,
                    friction: 10,
                    tension: 100
                }
            ).start();
            Animated.spring(
                this.state.iconButtonPadding,
                {
                    toValue: 5,
                    friction: 10,
                    tension: 100
                }
            ).start();
        } else if (this.state.oldScrollOffsetY < 60) {
            Animated.spring(
                this.state.headerHeight,
                {
                    toValue: 60,
                    friction: 10,
                    tension: 100
                }
            ).start();
            Animated.spring(
                this.state.iconButtonSize,
                {
                    toValue: 20,
                    friction: 10,
                    tension: 100
                }
            ).start();
            Animated.spring(
                this.state.iconButtonPadding,
                {
                    toValue: 10,
                    friction: 10,
                    tension: 100
                }
            ).start();
        }
    }

    componentDidUpdate() {

    }

    _orientationDidChange = (changedOrientation) => {
        if (changedOrientation === 'PORTRAIT') {
            this.setState({ orientation: changedOrientation });
        } else if (changedOrientation === 'LANDSCAPE') {
            this.setState({ orientation: changedOrientation });
        }
    };

    _handleAppStateChange = (appState) => {
        if (appState === "background") {
            let newOrientation = Orientation.getInitialOrientation();
            if (newOrientation === 'PORTRAIT') {
                this.setState({ orientation: newOrientation });
            } else if (newOrientation === 'LANDSCAPE') {
                this.setState({ orientation: newOrientation });
            }
        }
    };

    _onMomentumScrollEnd(e, state, context) {
        switch (state.index) {
            case 0:
                this.props.selectDay('Mon');
                break;
            case 1:
                this.props.selectDay('Tue');
                break;
            case 2:
                this.props.selectDay('Wed');
                break;
            case 3:
                this.props.selectDay('Thu');
                break;
            case 4:
                this.props.selectDay('Fri');
                break;
        }
    }

    handleScrollDirection(scroll) {
        let currentOffsetY = scroll.nativeEvent.contentOffset.y;
        let scrollDirectionDown = currentOffsetY > this.state.oldScrollOffsetY;
        this.setState({ oldScrollOffsetY: currentOffsetY, scrollDirectionDown: scrollDirectionDown });
    }

    handleScrollToWeekViewTimeslot() {
        let currentTime = parseInt(moment().format('HMM'));

        if (this.props.scrollToTimeslot && this.state.doneInitialScrollWeekView === false && currentTime >= 815 && currentTime <= 1915) {
            let scrollOffsetY;

            if (currentTime > 815 && currentTime < 900) {
                scrollOffsetY = 50;
            } else if (currentTime > 900 && currentTime < 945) {
                scrollOffsetY = 150;
            } else if (currentTime > 945 && currentTime < 1045) {
                scrollOffsetY = 250;
            } else if (currentTime > 1045 && currentTime < 1130) {
                scrollOffsetY = 350;
            } else if (currentTime > 1130 && currentTime < 1230) {
                scrollOffsetY = 450;
            } else if (currentTime > 1230 && currentTime < 1315) {
                scrollOffsetY = 550;
            } else if (currentTime > 1315 && currentTime < 1415) {
                scrollOffsetY = 650;
            } else if (currentTime > 1415 && currentTime < 1500) {
                scrollOffsetY = 750;
            } else if (currentTime > 1500 && currentTime < 1545) {
                scrollOffsetY = 850;
            } else if (currentTime > 1545 && currentTime < 1645) {
                scrollOffsetY = 950;
            } else if (currentTime > 1645 && currentTime < 1730) {
                scrollOffsetY = 1050;
            } else if (currentTime > 1730 && currentTime < 1830) {
                scrollOffsetY = 1150;
            } else if (currentTime > 1830 && currentTime < 1915) {
                scrollOffsetY = 1250;
            }

            this.setState({ oldScrollOffsetY: scrollOffsetY });

            setTimeout(() => {
                this.scrollWeekView.scrollTo({ y: scrollOffsetY });
            }, 500);
            // Using 500 milliseconds delay here because otherwise it looks really confusing to the user why the whole 'View' just jumped downwards
        }

        this.setState({ doneInitialScrollWeekView: true });
    }

    renderTimetable() {
        if (this.props.loading) {
            return (
                <View style={styles.spinner}>
                    <ActivityIndicator size={'large'} />
                </View>
            );
        } else {
            if (this.props.week && this.props.slots) {
                if (this.state.orientation === 'PORTRAIT') {  // Render: DayView
                    let eventsList = JSON.parse(this.props.week)["events"];
                    let eventsMon = [];
                    let eventIndexMon = 0;
                    let eventsTue = [];
                    let eventIndexTue = 0;
                    let eventsWed = [];
                    let eventIndexWed = 0;
                    let eventsThu = [];
                    let eventIndexThu = 0;
                    let eventsFri = [];
                    let eventIndexFri = 0;

                    for (let event in eventsList) {
                        switch (eventsList[event]["day"]) {
                            case 'mon':
                                eventsMon.push(eventsList[event]);
                                eventsMon[eventIndexMon]["slot"] = `${this.props.slots[eventsList[event]["start"]].start} - ${this.props.slots[eventsList[event]["end"]].end}`;
                                eventIndexMon += 1;  // Increase the actual index by one every iteration the if-statement passes
                                break;
                            case 'tue':
                                eventsTue.push(eventsList[event]);
                                eventsTue[eventIndexTue]["slot"] = `${this.props.slots[eventsList[event]["start"]].start} - ${this.props.slots[eventsList[event]["end"]].end}`;
                                eventIndexTue += 1;  // Increase the actual index by one every iteration the if-statement passes
                                break;
                            case 'wed':
                                eventsWed.push(eventsList[event]);
                                eventsWed[eventIndexWed]["slot"] = `${this.props.slots[eventsList[event]["start"]].start} - ${this.props.slots[eventsList[event]["end"]].end}`;
                                eventIndexWed += 1;  // Increase the actual index by one every iteration the if-statement passes
                                break;
                            case 'thu':
                                eventsThu.push(eventsList[event]);
                                eventsThu[eventIndexThu]["slot"] = `${this.props.slots[eventsList[event]["start"]].start} - ${this.props.slots[eventsList[event]["end"]].end}`;
                                eventIndexThu += 1;  // Increase the actual index by one every iteration the if-statement passes
                                break;
                            case 'fri':
                                eventsFri.push(eventsList[event]);
                                eventsFri[eventIndexFri]["slot"] = `${this.props.slots[eventsList[event]["start"]].start} - ${this.props.slots[eventsList[event]["end"]].end}`;
                                eventIndexFri += 1;  // Increase the actual index by one every iteration the if-statement passes
                                break;
                        }
                    }

                    let swiperIndex = -1;

                    switch (this.props.selectedDay) {
                        case 'mon':
                            swiperIndex = 0;
                            break;
                        case 'tue':
                            swiperIndex = 1;
                            break;
                        case 'wed':
                            swiperIndex = 2;
                            break;
                        case 'thu':
                            swiperIndex = 3;
                            break;
                        case 'fri':
                            swiperIndex = 4;
                            break;
                    }

                    let day = moment().day(this.props.selectedDay).week(this.props.currentWeek).format('ddd');
                    let date = moment().day(this.props.selectedDay).week(this.props.currentWeek).format('DD.MM.YYYY');

                    return (
                        <View
                            onLayout={(event) => { this.setState({swiperHeight: event.nativeEvent.layout.height}); }}  // Set the height of the 'View' for the 'Swiper'
                            style={styles.dayView}
                        >
                            <ScrollView ref={(scrollView) => this.scrollDayView = scrollView }>
                                <Swiper
                                    loop={false}
                                    showsPagination={false}
                                    index={swiperIndex}
                                    height={this.state.swiperHeight}
                                    onMomentumScrollEnd={this._onMomentumScrollEnd.bind(this)}
                                >
                                    <DayView events={eventsMon} />
                                    <DayView events={eventsTue} />
                                    <DayView events={eventsWed} />
                                    <DayView events={eventsThu} />
                                    <DayView events={eventsFri} />
                                </Swiper>
                            </ScrollView>
                            <DaySwitcher day={i18n.t(day) + ', ' + date} />
                        </View>
                    );
                } else if (this.state.orientation === 'LANDSCAPE') {  // Render: WeekView
                    let eventsWeek = JSON.parse(this.props.week)["events"];

                    return (
                        <ScrollView
                            onLayout={this.handleScrollToWeekViewTimeslot.bind(this)}
                            onScroll={this.handleScrollDirection.bind(this)}
                            ref={(scrollView) => this.scrollWeekView = scrollView }
                            style={styles.weekView}>
                            <WeekView events={eventsWeek} />
                        </ScrollView>
                    );
                }
            }
        }
    }

    renderHeader() {
        return (
            <Animated.View style={[styles.header, { height: this.state.headerHeight["_value"] }]}>
                <IconButton
                    onPress={() => {this.props.fetchWeek(this.props.user, this.props.program, (this.props.currentWeek - 1), this.props.semester); this.props.selectDay('Mon');}}
                    style={{ paddingTop: this.state.iconButtonPadding["_value"], paddingBottom: this.state.iconButtonPadding["_value"] }}
                >
                    <Image
                        source={require('../assets/images/backwards.png')}
                        style={{ height: this.state.iconButtonSize["_value"], width: this.state.iconButtonSize["_value"] }}
                    />
                </IconButton>
                <View style={styles.headerView}>
                    <Text style={styles.headerText}>
                        {i18n.t('week_of_the_year')} {this.props.currentWeek}
                    </Text>
                    <Text>
                        {this.findAnnotation()}
                    </Text>
                </View>
                <IconButton
                    onPress={() => {this.props.fetchWeek(this.props.user, this.props.program, (this.props.currentWeek + 1), this.props.semester); this.props.selectDay('Mon');}}
                    style={{ paddingTop: this.state.iconButtonPadding["_value"], paddingBottom: this.state.iconButtonPadding["_value"] }}
                >
                    <Image
                        source={require('../assets/images/forward.png')}
                        style={{ height: this.state.iconButtonSize["_value"], width: this.state.iconButtonSize["_value"] }}
                    />
                </IconButton>
            </Animated.View>
        );
    }

    findAnnotation() {
        let weeks = JSON.parse(this.props.masterdata)["timetable"]["weeks"]["kw" + this.props.currentWeek];
        if (weeks !== undefined) {
            return weeks["annotation"];
        }
    }

    render() {
        return (
            <View style={{ flex: 1 }}>
                {this.renderHeader()}
                {this.renderTimetable()}
            </View>
        );
    }
}

i18n.locale = 'de';
i18n.fallbacks = true;
i18n.translations = bundledTranslations;

const styles = {
    dayView: {
        flex: 1,
        flexDirection: "column"
    },
    weekView: {
        flex: 1
    },
    header: {
        flexDirection: "row",
        justifyContent: "space-between",
        alignItems: "center",
        backgroundColor: "#F4F4F4",
        elevation: 5,
        borderBottomWidth: 1,
        borderBottomColor: "#CCCCCC",
        padding: 10
    },
    headerText: {
        fontSize: 20,
        fontWeight: "bold"
    },
    headerView: {
        flexDirection: "column",
        alignItems: "center"
    },
    spinner: {
        flex: 1,
        justifyContent: "center",
        alignItems: "center"
    }
};

const mapStateToProps = state => {
    return {
        user: state.login.user,
        program: state.login.program,
        masterdata: state.timetable.masterdata,
        slots: state.timetable.timeslots,
        week: state.timetable.fetchedWeek,
        currentWeek: state.timetable.currentWeek,
        selectedDay: state.timetable.selectedDay,
        loading: state.timetable.loadingFetch,
        semester: state.settings.semester,
        scrollToTimeslot: state.settings.scrollToTimeslot
    }
};

export default connect(mapStateToProps, { fetchWeek, selectDay })(Timetable);
<commit_msg>Reverted an unwanted change from commit 32c3bc5<commit_after>import React, { Component } from 'react';
import {
    Text,
    View,
    ScrollView,
    Image,
    ActivityIndicator,
    AsyncStorage,
    Animated,
    LayoutAnimation,
    UIManager,
    AppState
} from 'react-native';
import { connect } from 'react-redux';
import moment from 'moment';
import Orientation from 'react-native-orientation';
import Swiper from 'react-native-swiper'
import { IconButton } from './common';
import { fetchWeek, selectDay } from '../actions';
import i18n from 'react-native-i18n';
import bundledTranslations from '../translations';
import DayView from './DayViewComponent';
import DaySwitcher from './DaySwitcherComponent';
import WeekView from './WeekViewComponent';

class Timetable extends Component {
    state = {
        headerHeight: new Animated.Value(60),
        iconButtonSize: new Animated.Value(20),
        iconButtonPadding: new Animated.Value(10),
        doneInitialScrollWeekView: false
    };

    componentWillMount() {
        setTimeout(() => { this.props.fetchWeek(this.props.user, this.props.program, '17', this.props.semester); }, 1000);  // Using the 17th week of the year to get results from the API
        // TODO: Try to refactor the line above so one does not need the 'setTimeout()'

        let initialOrientation = Orientation.getInitialOrientation();
        if (initialOrientation === 'PORTRAIT') {
            this.setState({ orientation: initialOrientation });
        } else if (initialOrientation === 'LANDSCAPE') {
            this.setState({ orientation: initialOrientation });
        }

        Orientation.addOrientationListener(this._orientationDidChange);

        AppState.addEventListener('change', this._handleAppStateChange.bind(this));

        let today = moment().format('ddd');
        let hour = moment().format('H');
        if (today === "Sat" || today === "Sun") {  // If it's on a weekend, jump to the next Monday
            this.props.selectDay('Mon');
        } else if (hour >= 19 && today === "Fri") {  // If it's after 19:00 and a Friday, jump to the next Monday
            this.props.selectDay('Mon');
        } else if (hour >= 19 && today !== "Fri") {  // If it's after 19:00, jump to the following day of the week
            today = moment().add(1, 'day').format('ddd');
            this.props.selectDay(today);
        } else {
            this.props.selectDay(today);
        }
    }

    componentWillUpdate() {
        // Had to disable the animation in virtue of it causing the 'DayView' to freeze during the rendering
        // UIManager.setLayoutAnimationEnabledExperimental && UIManager.setLayoutAnimationEnabledExperimental(true);
        // LayoutAnimation.easeInEaseOut();
    }

    componentDidUpdate() {
        if (this.state.oldScrollOffsetY > 90) {
            Animated.spring(
                this.state.headerHeight,
                {
                    toValue: 35,
                    friction: 10,
                    tension: 100
                }
            ).start();
            Animated.spring(
                this.state.iconButtonSize,
                {
                    toValue: 10,
                    friction: 10,
                    tension: 100
                }
            ).start();
            Animated.spring(
                this.state.iconButtonPadding,
                {
                    toValue: 5,
                    friction: 10,
                    tension: 100
                }
            ).start();
        } else if (this.state.oldScrollOffsetY < 60) {
            Animated.spring(
                this.state.headerHeight,
                {
                    toValue: 60,
                    friction: 10,
                    tension: 100
                }
            ).start();
            Animated.spring(
                this.state.iconButtonSize,
                {
                    toValue: 20,
                    friction: 10,
                    tension: 100
                }
            ).start();
            Animated.spring(
                this.state.iconButtonPadding,
                {
                    toValue: 10,
                    friction: 10,
                    tension: 100
                }
            ).start();
        }
    }

    _orientationDidChange = (changedOrientation) => {
        if (changedOrientation === 'PORTRAIT') {
            this.setState({ orientation: changedOrientation });
        } else if (changedOrientation === 'LANDSCAPE') {
            this.setState({ orientation: changedOrientation });
        }
    };

    _handleAppStateChange = (appState) => {
        if (appState === "background") {
            let newOrientation = Orientation.getInitialOrientation();
            if (newOrientation === 'PORTRAIT') {
                this.setState({ orientation: newOrientation });
            } else if (newOrientation === 'LANDSCAPE') {
                this.setState({ orientation: newOrientation });
            }
        }
    };

    _onMomentumScrollEnd(e, state, context) {
        switch (state.index) {
            case 0:
                this.props.selectDay('Mon');
                break;
            case 1:
                this.props.selectDay('Tue');
                break;
            case 2:
                this.props.selectDay('Wed');
                break;
            case 3:
                this.props.selectDay('Thu');
                break;
            case 4:
                this.props.selectDay('Fri');
                break;
        }
    }

    handleScrollDirection(scroll) {
        let currentOffsetY = scroll.nativeEvent.contentOffset.y;
        let scrollDirectionDown = currentOffsetY > this.state.oldScrollOffsetY;
        this.setState({ oldScrollOffsetY: currentOffsetY, scrollDirectionDown: scrollDirectionDown });
    }

    handleScrollToWeekViewTimeslot() {
        let currentTime = parseInt(moment().format('HMM'));

        if (this.props.scrollToTimeslot && this.state.doneInitialScrollWeekView === false && currentTime >= 815 && currentTime <= 1915) {
            let scrollOffsetY;

            if (currentTime > 815 && currentTime < 900) {
                scrollOffsetY = 50;
            } else if (currentTime > 900 && currentTime < 945) {
                scrollOffsetY = 150;
            } else if (currentTime > 945 && currentTime < 1045) {
                scrollOffsetY = 250;
            } else if (currentTime > 1045 && currentTime < 1130) {
                scrollOffsetY = 350;
            } else if (currentTime > 1130 && currentTime < 1230) {
                scrollOffsetY = 450;
            } else if (currentTime > 1230 && currentTime < 1315) {
                scrollOffsetY = 550;
            } else if (currentTime > 1315 && currentTime < 1415) {
                scrollOffsetY = 650;
            } else if (currentTime > 1415 && currentTime < 1500) {
                scrollOffsetY = 750;
            } else if (currentTime > 1500 && currentTime < 1545) {
                scrollOffsetY = 850;
            } else if (currentTime > 1545 && currentTime < 1645) {
                scrollOffsetY = 950;
            } else if (currentTime > 1645 && currentTime < 1730) {
                scrollOffsetY = 1050;
            } else if (currentTime > 1730 && currentTime < 1830) {
                scrollOffsetY = 1150;
            } else if (currentTime > 1830 && currentTime < 1915) {
                scrollOffsetY = 1250;
            }

            this.setState({ oldScrollOffsetY: scrollOffsetY });

            setTimeout(() => {
                this.scrollWeekView.scrollTo({ y: scrollOffsetY });
            }, 500);
            // Using 500 milliseconds delay here because otherwise it looks really confusing to the user why the whole 'View' just jumped downwards
        }

        this.setState({ doneInitialScrollWeekView: true });
    }

    renderTimetable() {
        if (this.props.loading) {
            return (
                <View style={styles.spinner}>
                    <ActivityIndicator size={'large'} />
                </View>
            );
        } else {
            if (this.props.week && this.props.slots) {
                if (this.state.orientation === 'PORTRAIT') {  // Render: DayView
                    let eventsList = JSON.parse(this.props.week)["events"];
                    let eventsMon = [];
                    let eventIndexMon = 0;
                    let eventsTue = [];
                    let eventIndexTue = 0;
                    let eventsWed = [];
                    let eventIndexWed = 0;
                    let eventsThu = [];
                    let eventIndexThu = 0;
                    let eventsFri = [];
                    let eventIndexFri = 0;

                    for (let event in eventsList) {
                        switch (eventsList[event]["day"]) {
                            case 'mon':
                                eventsMon.push(eventsList[event]);
                                eventsMon[eventIndexMon]["slot"] = `${this.props.slots[eventsList[event]["start"]].start} - ${this.props.slots[eventsList[event]["end"]].end}`;
                                eventIndexMon += 1;  // Increase the actual index by one every iteration the if-statement passes
                                break;
                            case 'tue':
                                eventsTue.push(eventsList[event]);
                                eventsTue[eventIndexTue]["slot"] = `${this.props.slots[eventsList[event]["start"]].start} - ${this.props.slots[eventsList[event]["end"]].end}`;
                                eventIndexTue += 1;  // Increase the actual index by one every iteration the if-statement passes
                                break;
                            case 'wed':
                                eventsWed.push(eventsList[event]);
                                eventsWed[eventIndexWed]["slot"] = `${this.props.slots[eventsList[event]["start"]].start} - ${this.props.slots[eventsList[event]["end"]].end}`;
                                eventIndexWed += 1;  // Increase the actual index by one every iteration the if-statement passes
                                break;
                            case 'thu':
                                eventsThu.push(eventsList[event]);
                                eventsThu[eventIndexThu]["slot"] = `${this.props.slots[eventsList[event]["start"]].start} - ${this.props.slots[eventsList[event]["end"]].end}`;
                                eventIndexThu += 1;  // Increase the actual index by one every iteration the if-statement passes
                                break;
                            case 'fri':
                                eventsFri.push(eventsList[event]);
                                eventsFri[eventIndexFri]["slot"] = `${this.props.slots[eventsList[event]["start"]].start} - ${this.props.slots[eventsList[event]["end"]].end}`;
                                eventIndexFri += 1;  // Increase the actual index by one every iteration the if-statement passes
                                break;
                        }
                    }

                    let swiperIndex = -1;

                    switch (this.props.selectedDay) {
                        case 'mon':
                            swiperIndex = 0;
                            break;
                        case 'tue':
                            swiperIndex = 1;
                            break;
                        case 'wed':
                            swiperIndex = 2;
                            break;
                        case 'thu':
                            swiperIndex = 3;
                            break;
                        case 'fri':
                            swiperIndex = 4;
                            break;
                    }

                    let day = moment().day(this.props.selectedDay).week(this.props.currentWeek).format('ddd');
                    let date = moment().day(this.props.selectedDay).week(this.props.currentWeek).format('DD.MM.YYYY');

                    return (
                        <View
                            onLayout={(event) => { this.setState({swiperHeight: event.nativeEvent.layout.height}); }}  // Set the height of the 'View' for the 'Swiper'
                            style={styles.dayView}
                        >
                            <ScrollView ref={(scrollView) => this.scrollDayView = scrollView }>
                                <Swiper
                                    loop={false}
                                    showsPagination={false}
                                    index={swiperIndex}
                                    height={this.state.swiperHeight}
                                    onMomentumScrollEnd={this._onMomentumScrollEnd.bind(this)}
                                >
                                    <DayView events={eventsMon} />
                                    <DayView events={eventsTue} />
                                    <DayView events={eventsWed} />
                                    <DayView events={eventsThu} />
                                    <DayView events={eventsFri} />
                                </Swiper>
                            </ScrollView>
                            <DaySwitcher day={i18n.t(day) + ', ' + date} />
                        </View>
                    );
                } else if (this.state.orientation === 'LANDSCAPE') {  // Render: WeekView
                    let eventsWeek = JSON.parse(this.props.week)["events"];

                    return (
                        <ScrollView
                            onLayout={this.handleScrollToWeekViewTimeslot.bind(this)}
                            onScroll={this.handleScrollDirection.bind(this)}
                            ref={(scrollView) => this.scrollWeekView = scrollView }
                            style={styles.weekView}>
                            <WeekView events={eventsWeek} />
                        </ScrollView>
                    );
                }
            }
        }
    }

    renderHeader() {
        return (
            <Animated.View style={[styles.header, { height: this.state.headerHeight["_value"] }]}>
                <IconButton
                    onPress={() => {this.props.fetchWeek(this.props.user, this.props.program, (this.props.currentWeek - 1), this.props.semester); this.props.selectDay('Mon');}}
                    style={{ paddingTop: this.state.iconButtonPadding["_value"], paddingBottom: this.state.iconButtonPadding["_value"] }}
                >
                    <Image
                        source={require('../assets/images/backwards.png')}
                        style={{ height: this.state.iconButtonSize["_value"], width: this.state.iconButtonSize["_value"] }}
                    />
                </IconButton>
                <View style={styles.headerView}>
                    <Text style={styles.headerText}>
                        {i18n.t('week_of_the_year')} {this.props.currentWeek}
                    </Text>
                    <Text>
                        {this.findAnnotation()}
                    </Text>
                </View>
                <IconButton
                    onPress={() => {this.props.fetchWeek(this.props.user, this.props.program, (this.props.currentWeek + 1), this.props.semester); this.props.selectDay('Mon');}}
                    style={{ paddingTop: this.state.iconButtonPadding["_value"], paddingBottom: this.state.iconButtonPadding["_value"] }}
                >
                    <Image
                        source={require('../assets/images/forward.png')}
                        style={{ height: this.state.iconButtonSize["_value"], width: this.state.iconButtonSize["_value"] }}
                    />
                </IconButton>
            </Animated.View>
        );
    }

    findAnnotation() {
        let weeks = JSON.parse(this.props.masterdata)["timetable"]["weeks"]["kw" + this.props.currentWeek];
        if (weeks !== undefined) {
            return weeks["annotation"];
        }
    }

    render() {
        return (
            <View style={{ flex: 1 }}>
                {this.renderHeader()}
                {this.renderTimetable()}
            </View>
        );
    }
}

i18n.locale = 'de';
i18n.fallbacks = true;
i18n.translations = bundledTranslations;

const styles = {
    dayView: {
        flex: 1,
        flexDirection: "column"
    },
    weekView: {
        flex: 1
    },
    header: {
        flexDirection: "row",
        justifyContent: "space-between",
        alignItems: "center",
        backgroundColor: "#F4F4F4",
        elevation: 5,
        borderBottomWidth: 1,
        borderBottomColor: "#CCCCCC",
        padding: 10
    },
    headerText: {
        fontSize: 20,
        fontWeight: "bold"
    },
    headerView: {
        flexDirection: "column",
        alignItems: "center"
    },
    spinner: {
        flex: 1,
        justifyContent: "center",
        alignItems: "center"
    }
};

const mapStateToProps = state => {
    return {
        user: state.login.user,
        program: state.login.program,
        masterdata: state.timetable.masterdata,
        slots: state.timetable.timeslots,
        week: state.timetable.fetchedWeek,
        currentWeek: state.timetable.currentWeek,
        selectedDay: state.timetable.selectedDay,
        loading: state.timetable.loadingFetch,
        semester: state.settings.semester,
        scrollToTimeslot: state.settings.scrollToTimeslot
    }
};

export default connect(mapStateToProps, { fetchWeek, selectDay })(Timetable);
<|endoftext|>


<commit_before>import React from 'react';
import styled from '@emotion/styled';
import { faClock, faEdit } from '@fortawesome/free-regular-svg-icons';
import {
  faDirections,
  faList,
  faTimes,
  faCheck,
  faPoundSign,
  faBaby,
  faWheelchair,
  faVenusMars,
  faGenderless,
  faKey,
  faCog,
  faQuestion,
  faChevronDown,
} from '@fortawesome/free-solid-svg-icons';
import { DateTime } from 'luxon';
import { Link } from 'react-router-dom';
import { useMutation, gql } from '@apollo/client';
import useComponentSize from '@rehooks/component-size';

import Box from './Box';
import Button from './Button';
import Text from './Text';
import Spacer from './Spacer';
import Icon from './Icon';
import { Media } from './Media';
import {
  getOpeningTimes,
  getIsOpen,
  WEEKDAYS,
  rangeTypes,
} from '../openingHours';

const Grid = styled(Box)`
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  margin: -${({ theme }) => theme.space[3]}px;
`;

const UnstyledList = styled.ul`
  list-style: none;
`;

function getTimeRangeLabel(range) {
  if (range === rangeTypes.CLOSED) {
    return 'Closed';
  }

  if (range.length === 2) {
    return range.join(' - ');
  }

  return 'Unknown';
}

function getIsOpenLabel(openingTimes = [], dateTime = DateTime.local()) {
  const isOpen = getIsOpen(openingTimes, dateTime);

  if (isOpen === null) {
    return 'Unknown';
  }

  return isOpen ? 'Open now' : 'Closed';
}

const SUBMIT_VERIFICATION_REPORT_MUTATION = gql`
  mutation submitVerificationReportMutation($id: ID) {
    submitVerificationReport(id: $id) {
      loo {
        id
        verifiedAt
      }
    }
  }
`;

const ToiletDetailsPanel = ({ data, isLoading, onDimensionsChange }) => {
  const [isExpanded, setIsExpanded] = React.useState(false);

  const [
    submitVerificationReport,
    { loading: submitVerificationLoading },
  ] = useMutation(SUBMIT_VERIFICATION_REPORT_MUTATION);

  // programmatically set focus on close button when panel expands
  const closeButtonRef = React.useRef(null);
  React.useEffect(() => {
    if (isExpanded && closeButtonRef.current) {
      closeButtonRef.current.focus();
    }
  }, [isExpanded]);

  const overflowRef = React.useRef(null);
  const handleBackToTopClick = () => {
    if (!overflowRef.current) {
      return;
    }
    overflowRef.current.scrollTop = 0;
  };

  // call onDimensionsChange whenever the dimensions of the container change
  const containerRef = React.useRef(null);
  const size = useComponentSize(containerRef);
  React.useEffect(() => {
    onDimensionsChange(size);
  }, [size, onDimensionsChange]);

  if (isLoading) {
    return (
      <Box
        width="100%"
        color="primary"
        bg="white"
        minHeight={100}
        borderTopLeftRadius={4}
        borderTopRightRadius={4}
        padding={4}
        display="flex"
        alignItems="center"
        ref={containerRef}
      >
        Loading toilet...
      </Box>
    );
  }

  const titleFragment = (
    <Text fontWeight="bold" fontSize={4}>

{data.name || 'Unnamed Toilet'}

    </Text>
  );

  const getDirectionsFragment = (
    <Button
      icon={<Icon icon={faDirections} />}
      as="a"
      href={`https://maps.apple.com/?dirflg=w&daddr=${[
        data.location.lat,
        data.location.lng,
      ]}`}
      target="_blank"
      rel="noopener noreferrer"
    >
      Get directions
    </Button>
  );

  const crossIconFragment = <Icon icon={faTimes} color="tertiary" />;
  const questionIconFragment = <Icon icon={faQuestion} color="tertiary" />;
  const checkIconFragment = <Icon icon={faCheck} />;

  const getFeatureValueIcon = (value, checkFunction) => {
    if (value === null) {
      return questionIconFragment;
    }

    if (checkFunction) {
      return checkFunction() ? checkIconFragment : crossIconFragment;
    }

    return value ? checkIconFragment : crossIconFragment;
  };

  const features = [
    {
      icon: <Icon icon={faPoundSign} />,
      label: 'Free',
      valueIcon: getFeatureValueIcon(!data.paymentRequired),
    },
    {
      icon: <Icon icon={faBaby} />,
      label: 'Baby Changing',
      valueIcon: getFeatureValueIcon(data.babyChange),
    },
    {
      icon: <Icon icon={faWheelchair} />,
      label: 'Accessible',
      valueIcon: getFeatureValueIcon(data.accessible),
    },
    ...(data.accessible
      ? [
          {
            icon: <Icon icon={faKey} />,
            label: 'RADAR Key',
            valueIcon: getFeatureValueIcon(data.radar),
          },
        ]
      : []),
    {
      icon: <Icon icon={faVenusMars} />,
      label: 'Unisex',
      valueIcon: getFeatureValueIcon(data.allGender),
    },
    {
      icon: <Icon icon={faGenderless} />,
      label: 'Gender Neutral',
      valueIcon: getFeatureValueIcon(data.allGender),
    },
    {
      icon: <Icon icon={faCog} />,
      label: 'Automatic',
      valueIcon: getFeatureValueIcon(data.automatic),
    },
  ];

  const openingTimes = getOpeningTimes(data.opening);
  const todayWeekdayIndex = DateTime.local().weekday - 1;

  const editUrl = `/loos/${data.id}/edit`;

  const updatedAt = DateTime.fromISO(data.updatedAt);
  let verifiedOrUpdatedDate = updatedAt;
  if (data.verifiedAt) {
    const verifiedAt = DateTime.fromISO(data.verifiedAt);
    if (updatedAt < verifiedAt) {
      verifiedOrUpdatedDate = verifiedAt;
    }
  }

  if (isExpanded) {
    return (
      <Box
        width="100%"
        color="primary"
        bg="white"
        borderTopLeftRadius={4}
        borderTopRightRadius={4}
        as="section"
        aria-labelledby="toilet-details-heading"
        ref={containerRef}
      >
        <Media greaterThanOrEqual="md">
          <Box position="absolute" top={30} right={30}>
            <button
              type="button"
              aria-label="Close toilet details"
              onClick={() => setIsExpanded(false)}
              aria-expanded="true"
              ref={closeButtonRef}
            >
              <Box
                height={26}
                width={26}
                display="flex"
                alignItems="center"
                justifyContent="center"
                borderRadius={13}
                borderColor="primary"
                borderWidth={1}
                borderStyle="solid"
              >
                <Icon icon={faTimes} />
              </Box>
            </button>
          </Box>
        </Media>

        <Media lessThan="md">
          <Box display="flex" justifyContent="center" paddingTop={2}>
            <Box
              as="button"
              type="button"
              aria-label="Close toilet details"
              onClick={() => setIsExpanded(false)}
              aria-expanded="true"
              padding={2}
              ref={closeButtonRef}
            >
              <Icon icon={faChevronDown} />
            </Box>
          </Box>
        </Media>

        <Box
          ref={overflowRef}
          maxHeight={400}
          overflow="auto"
          padding={4}
          paddingTop={[0, 4]}
          paddingRight={[4, 5]}
        >
          <Grid>
            <Box width={['100%', '50%', '25%']} padding={3}>
              {titleFragment}
              <Spacer mb={2} />
              {getDirectionsFragment}
              <Spacer mb={4} />
              <Text fontWeight="bold">

Is this information correct?

              </Text>
              <Spacer mb={2} />
              <Box display="flex" alignItems="center">
                <Button
                  onClick={() =>
                    submitVerificationReport({ variables: { id: data.id } })
                  }
                  disabled={submitVerificationLoading}
                >
                  Yes
                </Button>
                <Spacer mr={4} />
                <Box display="flex" alignItems="center">
                  No?
                  <Spacer mr={2} />
                  <Button
                    variant="secondary"
                    icon={<Icon icon={faEdit} />}
                    as={Link}
                    to={editUrl}
                  >
                    Edit
                  </Button>
                </Box>
              </Box>
              <Spacer mb={2} />
              Last verified:{' '}
              {verifiedOrUpdatedDate.toLocaleString(DateTime.DATETIME_SHORT)}
            </Box>

            <Box width={['100%', '50%', '25%']} padding={3} order={[0, 1]}>
              <Box display="flex" alignItems="center">
                <Icon icon={faClock} />
                <Spacer mr={2} />
                <Text fontWeight="bold">

Opening Hours

                </Text>
              </Box>
              <Spacer mb={2} />
              <UnstyledList>
                {openingTimes.map((timeRange, i) => (
                  <Box
                    as="li"
                    display="flex"
                    justifyContent="space-between"
                    key={i}
                    padding={1}
                    bg={i === todayWeekdayIndex ? 'ice' : 'white'}
                  >
                    {WEEKDAYS[i]}
                    {getTimeRangeLabel(timeRange)}
                  </Box>
                ))}
              </UnstyledList>
              <Spacer mb={2} />
              <Text fontSize={1} color="grey">
                Hours may vary with national holidays or seasonal changes. If
                you know these hours to be out of date please{' '}
                <Button as={Link} to={editUrl} variant="link">
                  edit this toilet
                </Button>
                .
              </Text>
            </Box>

            <Box width={['100%', '50%', '25%']} padding={3}>
              <Text fontWeight="bold">

Features

              </Text>
              <Spacer mb={2} />
              <UnstyledList>
                {features.map((feature) => (
                  <Box
                    as="li"
                    key={feature.label}
                    display="flex"
                    alignItems="center"
                    justifyContent="space-between"
                    mb={1}
                  >
                    <Box display="flex" alignItems="center">
                      <Box width="20px" display="flex" justifyContent="center">
                        {feature.icon}
                      </Box>
                      <Spacer mr={2} />
                      {feature.label}
                    </Box>
                    {feature.valueIcon}
                  </Box>
                ))}
              </UnstyledList>
            </Box>
            <Box width={['100%', '50%', '25%']} padding={3}>
              {Boolean(data.paymentRequired) && (
                <>
                  <Text fontWeight="bold">

Fee

                  </Text>
                  <Spacer mb={2} />
                  {data.paymentDetails}
                  <Spacer mb={3} />
                </>
              )}
              {Boolean(data.notes) && (
                <>
                  <Text fontWeight="bold">

Notes

                  </Text>
                  <Spacer mb={2} />

                    {data.notes.split('\n').map((string, i) => (

{string}

                    ))}

                </>
              )}
            </Box>

            <Box
              as={Media}
              lessThan="md"
              display="flex"
              justifyContent="center"
              width="100%"
              padding={2}
              marginBottom={2}
            >
              <Button variant="link" onClick={handleBackToTopClick}>
                Back to top
              </Button>
            </Box>
          </Grid>
        </Box>
      </Box>
    );
  }

  return (
    <Box
      width="100%"
      color="primary"
      bg="white"
      minHeight={100}
      borderTopLeftRadius={4}
      borderTopRightRadius={4}
      padding={4}
      as="section"
      aria-labelledby="toilet-details-heading"
      ref={containerRef}
    >
      <Grid>
        <Box width={['100%', '50%', '25%']} padding={3}>
          {titleFragment}
        </Box>

        <Box width={['100%', '50%', '25%']} padding={3}>
          <Box display="flex" alignItems="center">
            <Icon icon={faClock} />
            <Spacer mr={2} />
            <Text fontWeight="bold">

Opening Hours

            </Text>
          </Box>
          <Spacer mb={2} />
          {getIsOpenLabel(openingTimes)}
        </Box>

        <Box
          width={['100%', '50%']}
          padding={3}
          display="flex"
          justifyContent={['flex-start', 'flex-start', 'flex-end']}
          alignItems="center"
        >
          {getDirectionsFragment}
          <Spacer mr={2} />
          <Button
            variant="secondary"
            icon={<Icon icon={faList} />}
            onClick={() => setIsExpanded(true)}
            aria-expanded="false"
          >
            More details
          </Button>
        </Box>
      </Grid>
    </Box>
  );
};

export default ToiletDetailsPanel;
<commit_msg>fix: remove rendered fresnel div<commit_after>import React from 'react';
import styled from '@emotion/styled';
import { faClock, faEdit } from '@fortawesome/free-regular-svg-icons';
import {
  faDirections,
  faList,
  faTimes,
  faCheck,
  faPoundSign,
  faBaby,
  faWheelchair,
  faVenusMars,
  faGenderless,
  faKey,
  faCog,
  faQuestion,
  faChevronDown,
} from '@fortawesome/free-solid-svg-icons';
import { DateTime } from 'luxon';
import { Link } from 'react-router-dom';
import { useMutation, gql } from '@apollo/client';
import useComponentSize from '@rehooks/component-size';

import Box from './Box';
import Button from './Button';
import Text from './Text';
import Spacer from './Spacer';
import Icon from './Icon';
import { Media } from './Media';
import {
  getOpeningTimes,
  getIsOpen,
  WEEKDAYS,
  rangeTypes,
} from '../openingHours';

const Grid = styled(Box)`
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  margin: -${({ theme }) => theme.space[3]}px;
`;

const UnstyledList = styled.ul`
  list-style: none;
`;

function getTimeRangeLabel(range) {
  if (range === rangeTypes.CLOSED) {
    return 'Closed';
  }

  if (range.length === 2) {
    return range.join(' - ');
  }

  return 'Unknown';
}

function getIsOpenLabel(openingTimes = [], dateTime = DateTime.local()) {
  const isOpen = getIsOpen(openingTimes, dateTime);

  if (isOpen === null) {
    return 'Unknown';
  }

  return isOpen ? 'Open now' : 'Closed';
}

const SUBMIT_VERIFICATION_REPORT_MUTATION = gql`
  mutation submitVerificationReportMutation($id: ID) {
    submitVerificationReport(id: $id) {
      loo {
        id
        verifiedAt
      }
    }
  }
`;

const ToiletDetailsPanel = ({ data, isLoading, onDimensionsChange }) => {
  const [isExpanded, setIsExpanded] = React.useState(false);

  const [
    submitVerificationReport,
    { loading: submitVerificationLoading },
  ] = useMutation(SUBMIT_VERIFICATION_REPORT_MUTATION);

  // programmatically set focus on close button when panel expands
  const closeButtonRef = React.useRef(null);
  React.useEffect(() => {
    if (isExpanded && closeButtonRef.current) {
      closeButtonRef.current.focus();
    }
  }, [isExpanded]);

  const overflowRef = React.useRef(null);
  const handleBackToTopClick = () => {
    if (!overflowRef.current) {
      return;
    }
    overflowRef.current.scrollTop = 0;
  };

  // call onDimensionsChange whenever the dimensions of the container change
  const containerRef = React.useRef(null);
  const size = useComponentSize(containerRef);
  React.useEffect(() => {
    onDimensionsChange(size);
  }, [size, onDimensionsChange]);

  if (isLoading) {
    return (
      <Box
        width="100%"
        color="primary"
        bg="white"
        minHeight={100}
        borderTopLeftRadius={4}
        borderTopRightRadius={4}
        padding={4}
        display="flex"
        alignItems="center"
        ref={containerRef}
      >
        Loading toilet...
      </Box>
    );
  }

  const titleFragment = (
    <Text fontWeight="bold" fontSize={4}>

{data.name || 'Unnamed Toilet'}

    </Text>
  );

  const getDirectionsFragment = (
    <Button
      icon={<Icon icon={faDirections} />}
      as="a"
      href={`https://maps.apple.com/?dirflg=w&daddr=${[
        data.location.lat,
        data.location.lng,
      ]}`}
      target="_blank"
      rel="noopener noreferrer"
    >
      Get directions
    </Button>
  );

  const crossIconFragment = <Icon icon={faTimes} color="tertiary" />;
  const questionIconFragment = <Icon icon={faQuestion} color="tertiary" />;
  const checkIconFragment = <Icon icon={faCheck} />;

  const getFeatureValueIcon = (value, checkFunction) => {
    if (value === null) {
      return questionIconFragment;
    }

    if (checkFunction) {
      return checkFunction() ? checkIconFragment : crossIconFragment;
    }

    return value ? checkIconFragment : crossIconFragment;
  };

  const features = [
    {
      icon: <Icon icon={faPoundSign} />,
      label: 'Free',
      valueIcon: getFeatureValueIcon(!data.paymentRequired),
    },
    {
      icon: <Icon icon={faBaby} />,
      label: 'Baby Changing',
      valueIcon: getFeatureValueIcon(data.babyChange),
    },
    {
      icon: <Icon icon={faWheelchair} />,
      label: 'Accessible',
      valueIcon: getFeatureValueIcon(data.accessible),
    },
    ...(data.accessible
      ? [
          {
            icon: <Icon icon={faKey} />,
            label: 'RADAR Key',
            valueIcon: getFeatureValueIcon(data.radar),
          },
        ]
      : []),
    {
      icon: <Icon icon={faVenusMars} />,
      label: 'Unisex',
      valueIcon: getFeatureValueIcon(data.allGender),
    },
    {
      icon: <Icon icon={faGenderless} />,
      label: 'Gender Neutral',
      valueIcon: getFeatureValueIcon(data.allGender),
    },
    {
      icon: <Icon icon={faCog} />,
      label: 'Automatic',
      valueIcon: getFeatureValueIcon(data.automatic),
    },
  ];

  const openingTimes = getOpeningTimes(data.opening);
  const todayWeekdayIndex = DateTime.local().weekday - 1;

  const editUrl = `/loos/${data.id}/edit`;

  const updatedAt = DateTime.fromISO(data.updatedAt);
  let verifiedOrUpdatedDate = updatedAt;
  if (data.verifiedAt) {
    const verifiedAt = DateTime.fromISO(data.verifiedAt);
    if (updatedAt < verifiedAt) {
      verifiedOrUpdatedDate = verifiedAt;
    }
  }

  if (isExpanded) {
    return (
      <Box
        width="100%"
        color="primary"
        bg="white"
        borderTopLeftRadius={4}
        borderTopRightRadius={4}
        as="section"
        aria-labelledby="toilet-details-heading"
        ref={containerRef}
      >
        <Media greaterThanOrEqual="md">
          <Box position="absolute" top={30} right={30}>
            <button
              type="button"
              aria-label="Close toilet details"
              onClick={() => setIsExpanded(false)}
              aria-expanded="true"
              ref={closeButtonRef}
            >
              <Box
                height={26}
                width={26}
                display="flex"
                alignItems="center"
                justifyContent="center"
                borderRadius={13}
                borderColor="primary"
                borderWidth={1}
                borderStyle="solid"
              >
                <Icon icon={faTimes} />
              </Box>
            </button>
          </Box>
        </Media>

        <Media lessThan="md">
          <Box display="flex" justifyContent="center" paddingTop={2}>
            <Box
              as="button"
              type="button"
              aria-label="Close toilet details"
              onClick={() => setIsExpanded(false)}
              aria-expanded="true"
              padding={2}
              ref={closeButtonRef}
            >
              <Icon icon={faChevronDown} />
            </Box>
          </Box>
        </Media>

        <Box
          ref={overflowRef}
          maxHeight={400}
          overflow="auto"
          padding={4}
          paddingTop={[0, 4]}
          paddingRight={[4, 5]}
        >
          <Grid>
            <Box width={['100%', '50%', '25%']} padding={3}>
              {titleFragment}
              <Spacer mb={2} />
              {getDirectionsFragment}
              <Spacer mb={4} />
              <Text fontWeight="bold">

Is this information correct?

              </Text>
              <Spacer mb={2} />
              <Box display="flex" alignItems="center">
                <Button
                  onClick={() =>
                    submitVerificationReport({ variables: { id: data.id } })
                  }
                  disabled={submitVerificationLoading}
                >
                  Yes
                </Button>
                <Spacer mr={4} />
                <Box display="flex" alignItems="center">
                  No?
                  <Spacer mr={2} />
                  <Button
                    variant="secondary"
                    icon={<Icon icon={faEdit} />}
                    as={Link}
                    to={editUrl}
                  >
                    Edit
                  </Button>
                </Box>
              </Box>
              <Spacer mb={2} />
              Last verified:{' '}
              {verifiedOrUpdatedDate.toLocaleString(DateTime.DATETIME_SHORT)}
            </Box>

            <Box width={['100%', '50%', '25%']} padding={3} order={[0, 1]}>
              <Box display="flex" alignItems="center">
                <Icon icon={faClock} />
                <Spacer mr={2} />
                <Text fontWeight="bold">

Opening Hours

                </Text>
              </Box>
              <Spacer mb={2} />
              <UnstyledList>
                {openingTimes.map((timeRange, i) => (
                  <Box
                    as="li"
                    display="flex"
                    justifyContent="space-between"
                    key={i}
                    padding={1}
                    bg={i === todayWeekdayIndex ? 'ice' : 'white'}
                  >
                    {WEEKDAYS[i]}
                    {getTimeRangeLabel(timeRange)}
                  </Box>
                ))}
              </UnstyledList>
              <Spacer mb={2} />
              <Text fontSize={1} color="grey">
                Hours may vary with national holidays or seasonal changes. If
                you know these hours to be out of date please{' '}
                <Button as={Link} to={editUrl} variant="link">
                  edit this toilet
                </Button>
                .
              </Text>
            </Box>

            <Box width={['100%', '50%', '25%']} padding={3}>
              <Text fontWeight="bold">

Features

              </Text>
              <Spacer mb={2} />
              <UnstyledList>
                {features.map((feature) => (
                  <Box
                    as="li"
                    key={feature.label}
                    display="flex"
                    alignItems="center"
                    justifyContent="space-between"
                    mb={1}
                  >
                    <Box display="flex" alignItems="center">
                      <Box width="20px" display="flex" justifyContent="center">
                        {feature.icon}
                      </Box>
                      <Spacer mr={2} />
                      {feature.label}
                    </Box>
                    {feature.valueIcon}
                  </Box>
                ))}
              </UnstyledList>
            </Box>
            <Box width={['100%', '50%', '25%']} padding={3}>
              {Boolean(data.paymentRequired) && (
                <>
                  <Text fontWeight="bold">

Fee

                  </Text>
                  <Spacer mb={2} />
                  {data.paymentDetails}
                  <Spacer mb={3} />
                </>
              )}
              {Boolean(data.notes) && (
                <>
                  <Text fontWeight="bold">

Notes

                  </Text>
                  <Spacer mb={2} />

                    {data.notes.split('\n').map((string, i) => (

{string}

                    ))}

                </>
              )}
            </Box>

            <Media lessThan="md">
              <Box
                display="flex"
                justifyContent="center"
                width="100%"
                padding={2}
                marginBottom={2}
              >
                <Button variant="link" onClick={handleBackToTopClick}>
                  Back to top
                </Button>
              </Box>
            </Media>
          </Grid>
        </Box>
      </Box>
    );
  }

  return (
    <Box
      width="100%"
      color="primary"
      bg="white"
      minHeight={100}
      borderTopLeftRadius={4}
      borderTopRightRadius={4}
      padding={4}
      as="section"
      aria-labelledby="toilet-details-heading"
      ref={containerRef}
    >
      <Grid>
        <Box width={['100%', '50%', '25%']} padding={3}>
          {titleFragment}
        </Box>

        <Box width={['100%', '50%', '25%']} padding={3}>
          <Box display="flex" alignItems="center">
            <Icon icon={faClock} />
            <Spacer mr={2} />
            <Text fontWeight="bold">

Opening Hours

            </Text>
          </Box>
          <Spacer mb={2} />
          {getIsOpenLabel(openingTimes)}
        </Box>

        <Box
          width={['100%', '50%']}
          padding={3}
          display="flex"
          justifyContent={['flex-start', 'flex-start', 'flex-end']}
          alignItems="center"
        >
          {getDirectionsFragment}
          <Spacer mr={2} />
          <Button
            variant="secondary"
            icon={<Icon icon={faList} />}
            onClick={() => setIsExpanded(true)}
            aria-expanded="false"
          >
            More details
          </Button>
        </Box>
      </Grid>
    </Box>
  );
};

export default ToiletDetailsPanel;
<|endoftext|>


<commit_before>import { QBtn } from '../btn'
import TouchPan from '../../directives/touch-pan'
import { cssTransform } from '../../utils/dom'
import { isNumber } from '../../utils/is'
import { between, normalizeToInterval } from '../../utils/format'
import { start, stop } from '../../utils/animate'
import { decelerate, standard } from '../../utils/easing'
import { getEventKey } from '../../utils/event'
import FullscreenMixin from '../../mixins/fullscreen'

export default {
  name: 'q-carousel',
  mixins: [FullscreenMixin],
  directives: {
    TouchPan
  },
  props: {
    value: Number,
    color: {
      type: String,
      default: 'primary'
    },
    height: String,
    arrows: Boolean,
    infinite: Boolean,
    animation: {
      type: [Number, Boolean],
      default: true
    },
    easing: Function,
    swipeEasing: Function,
    noSwipe: Boolean,
    autoplay: [Number, Boolean],
    handleArrowKeys: Boolean,
    quickNav: Boolean,
    quickNavIcon: String
  },
  provide () {
    return {
      'carousel': this
    }
  },
  data () {
    return {
      position: 0,
      slide: 0,
      positionSlide: 0,
      slidesNumber: 0,
      animUid: false
    }
  },
  watch: {
    value (v) {
      if (v !== this.slide) {
        this.goToSlide(v)
      }
    },
    autoplay () {
      this.__planAutoPlay()
    },
    infinite () {
      this.__planAutoPlay()
    },
    handleArrowKeys (v) {
      this.__setArrowKeys(v)
    }
  },
  computed: {
    trackPosition () {
      return cssTransform(`translateX(${this.position}%)`)
    },
    infiniteLeft () {
      return this.infinite && this.slidesNumber > 1 && this.positionSlide < 0
    },
    infiniteRight () {
      return this.infinite && this.slidesNumber > 1 && this.positionSlide >= this.slidesNumber
    },
    canGoToPrevious () {
      return this.infinite ? this.slidesNumber > 1 : this.slide > 0
    },
    canGoToNext () {
      return this.infinite ? this.slidesNumber > 1 : this.slide < this.slidesNumber - 1
    },
    computedQuickNavIcon () {
      return this.quickNavIcon || this.$q.icon.carousel.quickNav
    },
    computedStyle () {
      if (!this.inFullscreen && this.height) {
        return `height: ${this.height}`
      }
    },
    slotScope () {
      return {
        slide: this.slide,
        slidesNumber: this.slidesNumber,
        percentage: this.slidesNumber < 2
          ? 100
          : 100 * this.slide / (this.slidesNumber - 1),
        goToSlide: this.goToSlide,
        previous: this.previous,
        next: this.next,
        color: this.color,
        inFullscreen: this.inFullscreen,
        toggleFullscreen: this.toggleFullscreen,
        canGoToNext: this.canGoToNext,
        canGoToPrevious: this.canGoToPrevious
      }
    }
  },
  methods: {
    previous () {
      return this.canGoToPrevious
        ? this.goToSlide(this.slide - 1)
        : Promise.resolve()
    },
    next () {
      return this.canGoToNext
        ? this.goToSlide(this.slide + 1)
        : Promise.resolve()
    },
    goToSlide (slide, fromSwipe = false) {
      return new Promise((resolve, reject) => {
        let
          direction = '',
          pos

        this.__cleanup()

        const finish = () => {
          this.$emit('input', this.slide)
          this.$emit('slide-direction', direction)
          this.__planAutoPlay()
          resolve()
        }

        if (this.slidesNumber < 2) {
          this.slide = 0
          this.positionSlide = 0
          pos = 0
        }
        else {
          if (!this.hasOwnProperty('initialPosition')) {
            this.position = -this.slide * 100
          }
          direction = slide > this.slide ? 'next' : 'previous'
          if (this.infinite) {
            this.slide = normalizeToInterval(slide, 0, this.slidesNumber - 1)
            pos = normalizeToInterval(slide, -1, this.slidesNumber)
            if (!fromSwipe) {
              this.positionSlide = pos
            }
          }
          else {
            this.slide = between(slide, 0, this.slidesNumber - 1)
            this.positionSlide = this.slide
            pos = this.slide
          }
        }

        pos = pos * -100

        if (!this.animation) {
          this.position = pos
          finish()
          return
        }

        this.animationInProgress = true

        this.animUid = start({
          from: this.position,
          to: pos,
          duration: isNumber(this.animation) ? this.animation : 300,
          easing: fromSwipe
            ? this.swipeEasing || decelerate
            : this.easing || standard,
          apply: pos => {
            this.position = pos
          },
          done: () => {
            if (this.infinite) {
              this.position = -this.slide * 100
              this.positionSlide = this.slide
            }
            this.animationInProgress = false
            finish()
          }
        })
      })
    },
    stopAnimation () {
      stop(this.animUid)
      this.animationInProgress = false
    },
    __pan (event) {
      if (this.infinite && this.animationInProgress) {
        return
      }
      if (event.isFirst) {
        this.initialPosition = this.position
        this.__cleanup()
      }

      let delta = (event.direction === 'left' ? -1 : 1) * event.distance.x

      if (
        (this.infinite && this.slidesNumber < 2) ||
        (
          !this.infinite &&
          (
            (this.slide === 0 && delta > 0) ||
            (this.slide === this.slidesNumber - 1 && delta < 0)
          )
        )
      ) {
        delta = delta / 10
      }

      this.position = this.initialPosition + delta / this.$refs.track.offsetWidth * 100
      this.positionSlide = (event.direction === 'left' ? this.slide + 1 : this.slide - 1)

      if (event.isFinal) {
        this.goToSlide(
          event.distance.x < 40
            ? this.slide
            : this.positionSlide,
          true
        ).then(() => {
          delete this.initialPosition
        })
      }
    },
    __planAutoPlay () {
      this.$nextTick(() => {
        if (this.autoplay) {
          clearTimeout(this.timer)
          this.timer = setTimeout(
            this.next,
            isNumber(this.autoplay) ? this.autoplay : 5000
          )
        }
      })
    },
    __cleanup () {
      this.stopAnimation()
      clearTimeout(this.timer)
    },
    __handleArrowKey (e) {
      const key = getEventKey(e)

      if (key === 37) { // left arrow key
        this.previous()
      }
      else if (key === 39) { // right arrow key
        this.next()
      }
    },
    __setArrowKeys (/* boolean */ state) {
      const op = `${state === true ? 'add' : 'remove'}EventListener`
      document[op]('keydown', this.__handleArrowKey)
    },
    __registerSlide () {
      this.slidesNumber++
    },
    __unregisterSlide () {
      this.slidesNumber--
    },
    __getScopedSlots (h) {
      if (this.slidesNumber === 0) {
        return
      }
      let slots = this.$scopedSlots
      if (slots) {
        return Object.keys(slots)
          .filter(key => key.startsWith('control-'))
          .map(key => slots[key](this.slotScope))
      }
    },
    __getQuickNav (h) {
      if (this.slidesNumber === 0 || !this.quickNav) {
        return
      }

      const
        slot = this.$scopedSlots['quick-nav'],
        items = []

      if (slot) {
        for (let i = 0; i < this.slidesNumber; i++) {
          items.push(slot({
            slide: i,
            before: i < this.slide,
            current: i === this.slide,
            after: i > this.slide,
            color: this.color,
            goToSlide: slide => { this.goToSlide(slide || i) }
          }))
        }
      }
      else {
        for (let i = 0; i < this.slidesNumber; i++) {
          items.push(h(QBtn, {
            key: i,
            'class': { inactive: i !== this.slide },
            props: {
              icon: this.computedQuickNavIcon,
              round: true,
              flat: true,
              dense: true,
              color: this.color
            },
            on: {
              click: () => {
                this.goToSlide(i)
              }
            }
          }))
        }
      }

      return h('div', {
        staticClass: 'q-carousel-quick-nav absolute-bottom scroll text-center',
        'class': `text-${this.color}`
      }, items)
    }
  },
  render (h) {
    return h('div', {
      staticClass: 'q-carousel',
      style: this.computedStyle,
      'class': { fullscreen: this.inFullscreen }
    }, [
      h('div', {
        staticClass: 'q-carousel-inner',
        directives: this.noSwipe
          ? null
          : [{
            name: 'touch-pan',
            modifiers: {
              horizontal: true,
              prevent: true,
              stop: true
            },
            value: this.__pan
          }]
      }, [
        h('div', {
          ref: 'track',
          staticClass: 'q-carousel-track',
          style: this.trackPosition,
          'class': {
            'infinite-left': this.infiniteLeft,
            'infinite-right': this.infiniteRight
          }
        }, [
          h('div', { staticClass: 'q-carousel-slide', style: `flex: 0 0 ${100}%`, directives: [{ name: 'show', value: this.infiniteRight }] }),
          this.$slots.default,
          h('div', { staticClass: 'q-carousel-slide', style: `flex: 0 0 ${100}%`, directives: [{ name: 'show', value: this.infiniteLeft }] })
        ])
      ]),
      this.arrows ? h(QBtn, {
        staticClass: 'q-carousel-left-arrow absolute',
        props: { color: this.color, icon: this.$q.icon.carousel.left, fabMini: true, flat: true },
        directives: [{ name: 'show', value: this.canGoToPrevious }],
        on: { click: this.previous }
      }) : null,
      this.arrows ? h(QBtn, {
        staticClass: 'q-carousel-right-arrow absolute',
        props: { color: this.color, icon: this.$q.icon.carousel.right, fabMini: true, flat: true },
        directives: [{ name: 'show', value: this.canGoToNext }],
        on: { click: this.next }
      }) : null,
      this.__getQuickNav(h),
      this.__getScopedSlots(h),
      this.$slots.control
    ])
  },
  mounted () {
    this.__planAutoPlay()
    if (this.handleArrowKeys) {
      this.__setArrowKeys(true)
    }
    this.__stopSlideNumberNotifier = this.$watch('slidesNumber', val => {
      this.$emit('slides-number', val)
      if (this.value >= val) {
        this.$emit('input', val - 1)
      }
    }, { immediate: true })
  },
  beforeDestroy () {
    this.__cleanup()
    this.__stopSlideNumberNotifier()
    if (this.handleArrowKeys) {
      this.__setArrowKeys(false)
    }
  }
}
<commit_msg>chore: Remove unnecessary event in QCarousel<commit_after>import { QBtn } from '../btn'
import TouchPan from '../../directives/touch-pan'
import { cssTransform } from '../../utils/dom'
import { isNumber } from '../../utils/is'
import { between, normalizeToInterval } from '../../utils/format'
import { start, stop } from '../../utils/animate'
import { decelerate, standard } from '../../utils/easing'
import { getEventKey } from '../../utils/event'
import FullscreenMixin from '../../mixins/fullscreen'

export default {
  name: 'q-carousel',
  mixins: [FullscreenMixin],
  directives: {
    TouchPan
  },
  props: {
    value: Number,
    color: {
      type: String,
      default: 'primary'
    },
    height: String,
    arrows: Boolean,
    infinite: Boolean,
    animation: {
      type: [Number, Boolean],
      default: true
    },
    easing: Function,
    swipeEasing: Function,
    noSwipe: Boolean,
    autoplay: [Number, Boolean],
    handleArrowKeys: Boolean,
    quickNav: Boolean,
    quickNavIcon: String
  },
  provide () {
    return {
      'carousel': this
    }
  },
  data () {
    return {
      position: 0,
      slide: 0,
      positionSlide: 0,
      slidesNumber: 0,
      animUid: false
    }
  },
  watch: {
    value (v) {
      if (v !== this.slide) {
        this.goToSlide(v)
      }
    },
    autoplay () {
      this.__planAutoPlay()
    },
    infinite () {
      this.__planAutoPlay()
    },
    handleArrowKeys (v) {
      this.__setArrowKeys(v)
    }
  },
  computed: {
    trackPosition () {
      return cssTransform(`translateX(${this.position}%)`)
    },
    infiniteLeft () {
      return this.infinite && this.slidesNumber > 1 && this.positionSlide < 0
    },
    infiniteRight () {
      return this.infinite && this.slidesNumber > 1 && this.positionSlide >= this.slidesNumber
    },
    canGoToPrevious () {
      return this.infinite ? this.slidesNumber > 1 : this.slide > 0
    },
    canGoToNext () {
      return this.infinite ? this.slidesNumber > 1 : this.slide < this.slidesNumber - 1
    },
    computedQuickNavIcon () {
      return this.quickNavIcon || this.$q.icon.carousel.quickNav
    },
    computedStyle () {
      if (!this.inFullscreen && this.height) {
        return `height: ${this.height}`
      }
    },
    slotScope () {
      return {
        slide: this.slide,
        slidesNumber: this.slidesNumber,
        percentage: this.slidesNumber < 2
          ? 100
          : 100 * this.slide / (this.slidesNumber - 1),
        goToSlide: this.goToSlide,
        previous: this.previous,
        next: this.next,
        color: this.color,
        inFullscreen: this.inFullscreen,
        toggleFullscreen: this.toggleFullscreen,
        canGoToNext: this.canGoToNext,
        canGoToPrevious: this.canGoToPrevious
      }
    }
  },
  methods: {
    previous () {
      return this.canGoToPrevious
        ? this.goToSlide(this.slide - 1)
        : Promise.resolve()
    },
    next () {
      return this.canGoToNext
        ? this.goToSlide(this.slide + 1)
        : Promise.resolve()
    },
    goToSlide (slide, fromSwipe = false) {
      return new Promise((resolve, reject) => {
        let
          direction = '',
          pos

        this.__cleanup()

        const finish = () => {
          this.$emit('input', this.slide)
          this.$emit('slide-direction', direction)
          this.__planAutoPlay()
          resolve()
        }

        if (this.slidesNumber < 2) {
          this.slide = 0
          this.positionSlide = 0
          pos = 0
        }
        else {
          if (!this.hasOwnProperty('initialPosition')) {
            this.position = -this.slide * 100
          }
          direction = slide > this.slide ? 'next' : 'previous'
          if (this.infinite) {
            this.slide = normalizeToInterval(slide, 0, this.slidesNumber - 1)
            pos = normalizeToInterval(slide, -1, this.slidesNumber)
            if (!fromSwipe) {
              this.positionSlide = pos
            }
          }
          else {
            this.slide = between(slide, 0, this.slidesNumber - 1)
            this.positionSlide = this.slide
            pos = this.slide
          }
        }

        pos = pos * -100

        if (!this.animation) {
          this.position = pos
          finish()
          return
        }

        this.animationInProgress = true

        this.animUid = start({
          from: this.position,
          to: pos,
          duration: isNumber(this.animation) ? this.animation : 300,
          easing: fromSwipe
            ? this.swipeEasing || decelerate
            : this.easing || standard,
          apply: pos => {
            this.position = pos
          },
          done: () => {
            if (this.infinite) {
              this.position = -this.slide * 100
              this.positionSlide = this.slide
            }
            this.animationInProgress = false
            finish()
          }
        })
      })
    },
    stopAnimation () {
      stop(this.animUid)
      this.animationInProgress = false
    },
    __pan (event) {
      if (this.infinite && this.animationInProgress) {
        return
      }
      if (event.isFirst) {
        this.initialPosition = this.position
        this.__cleanup()
      }

      let delta = (event.direction === 'left' ? -1 : 1) * event.distance.x

      if (
        (this.infinite && this.slidesNumber < 2) ||
        (
          !this.infinite &&
          (
            (this.slide === 0 && delta > 0) ||
            (this.slide === this.slidesNumber - 1 && delta < 0)
          )
        )
      ) {
        delta = delta / 10
      }

      this.position = this.initialPosition + delta / this.$refs.track.offsetWidth * 100
      this.positionSlide = (event.direction === 'left' ? this.slide + 1 : this.slide - 1)

      if (event.isFinal) {
        this.goToSlide(
          event.distance.x < 40
            ? this.slide
            : this.positionSlide,
          true
        ).then(() => {
          delete this.initialPosition
        })
      }
    },
    __planAutoPlay () {
      this.$nextTick(() => {
        if (this.autoplay) {
          clearTimeout(this.timer)
          this.timer = setTimeout(
            this.next,
            isNumber(this.autoplay) ? this.autoplay : 5000
          )
        }
      })
    },
    __cleanup () {
      this.stopAnimation()
      clearTimeout(this.timer)
    },
    __handleArrowKey (e) {
      const key = getEventKey(e)

      if (key === 37) { // left arrow key
        this.previous()
      }
      else if (key === 39) { // right arrow key
        this.next()
      }
    },
    __setArrowKeys (/* boolean */ state) {
      const op = `${state === true ? 'add' : 'remove'}EventListener`
      document[op]('keydown', this.__handleArrowKey)
    },
    __registerSlide () {
      this.slidesNumber++
    },
    __unregisterSlide () {
      this.slidesNumber--
    },
    __getScopedSlots (h) {
      if (this.slidesNumber === 0) {
        return
      }
      let slots = this.$scopedSlots
      if (slots) {
        return Object.keys(slots)
          .filter(key => key.startsWith('control-'))
          .map(key => slots[key](this.slotScope))
      }
    },
    __getQuickNav (h) {
      if (this.slidesNumber === 0 || !this.quickNav) {
        return
      }

      const
        slot = this.$scopedSlots['quick-nav'],
        items = []

      if (slot) {
        for (let i = 0; i < this.slidesNumber; i++) {
          items.push(slot({
            slide: i,
            before: i < this.slide,
            current: i === this.slide,
            after: i > this.slide,
            color: this.color,
            goToSlide: slide => { this.goToSlide(slide || i) }
          }))
        }
      }
      else {
        for (let i = 0; i < this.slidesNumber; i++) {
          items.push(h(QBtn, {
            key: i,
            'class': { inactive: i !== this.slide },
            props: {
              icon: this.computedQuickNavIcon,
              round: true,
              flat: true,
              dense: true,
              color: this.color
            },
            on: {
              click: () => {
                this.goToSlide(i)
              }
            }
          }))
        }
      }

      return h('div', {
        staticClass: 'q-carousel-quick-nav absolute-bottom scroll text-center',
        'class': `text-${this.color}`
      }, items)
    }
  },
  render (h) {
    return h('div', {
      staticClass: 'q-carousel',
      style: this.computedStyle,
      'class': { fullscreen: this.inFullscreen }
    }, [
      h('div', {
        staticClass: 'q-carousel-inner',
        directives: this.noSwipe
          ? null
          : [{
            name: 'touch-pan',
            modifiers: {
              horizontal: true,
              prevent: true,
              stop: true
            },
            value: this.__pan
          }]
      }, [
        h('div', {
          ref: 'track',
          staticClass: 'q-carousel-track',
          style: this.trackPosition,
          'class': {
            'infinite-left': this.infiniteLeft,
            'infinite-right': this.infiniteRight
          }
        }, [
          h('div', { staticClass: 'q-carousel-slide', style: `flex: 0 0 ${100}%`, directives: [{ name: 'show', value: this.infiniteRight }] }),
          this.$slots.default,
          h('div', { staticClass: 'q-carousel-slide', style: `flex: 0 0 ${100}%`, directives: [{ name: 'show', value: this.infiniteLeft }] })
        ])
      ]),
      this.arrows ? h(QBtn, {
        staticClass: 'q-carousel-left-arrow absolute',
        props: { color: this.color, icon: this.$q.icon.carousel.left, fabMini: true, flat: true },
        directives: [{ name: 'show', value: this.canGoToPrevious }],
        on: { click: this.previous }
      }) : null,
      this.arrows ? h(QBtn, {
        staticClass: 'q-carousel-right-arrow absolute',
        props: { color: this.color, icon: this.$q.icon.carousel.right, fabMini: true, flat: true },
        directives: [{ name: 'show', value: this.canGoToNext }],
        on: { click: this.next }
      }) : null,
      this.__getQuickNav(h),
      this.__getScopedSlots(h),
      this.$slots.control
    ])
  },
  mounted () {
    this.__planAutoPlay()
    if (this.handleArrowKeys) {
      this.__setArrowKeys(true)
    }
    this.__stopSlideNumberNotifier = this.$watch('slidesNumber', val => {
      if (this.value >= val) {
        this.$emit('input', val - 1)
      }
    }, { immediate: true })
  },
  beforeDestroy () {
    this.__cleanup()
    this.__stopSlideNumberNotifier()
    if (this.handleArrowKeys) {
      this.__setArrowKeys(false)
    }
  }
}
<|endoftext|>


<commit_before>import Support from '../../../utils/support';

export default function (index = 0, speed = this.params.speed, runCallbacks = true, internal) {
  const swiper = this;
  let slideIndex = index;
  if (slideIndex < 0) slideIndex = 0;

  const {
    params, snapGrid, slidesGrid, previousIndex, activeIndex, rtl, $wrapperEl,
  } = swiper;

  let snapIndex = Math.floor(slideIndex / params.slidesPerGroup);
  if (snapIndex >= snapGrid.length) snapIndex = snapGrid.length - 1;

  if ((activeIndex || params.initialSlide || 0) === (previousIndex || 0) && runCallbacks) {
    swiper.emit('beforeSlideChangeStart');
  }

  const translate = -snapGrid[snapIndex];

  // Update progress
  swiper.updateProgress(translate);

  // Normalize slideIndex
  if (params.normalizeSlideIndex) {
    for (let i = 0; i < slidesGrid.length; i += 1) {
      if (-Math.floor(translate * 100) >= Math.floor(slidesGrid[i] * 100)) {
        slideIndex = i;
      }
    }
  }

  // Directions locks
  if (swiper.initialized) {
    if (!swiper.allowSlideNext && translate < swiper.translate && translate < swiper.minTranslate()) {
      return false;
    }
    if (!swiper.allowSlidePrev && translate > swiper.translate && translate > swiper.maxTranslate()) {
      if ((activeIndex || 0) !== slideIndex) return false;
    }
  }

  // Update Index
  if ((rtl && -translate === swiper.translate) || (!rtl && translate === swiper.translate)) {
    swiper.updateActiveIndex(slideIndex);
    // Update Height
    if (params.autoHeight) {
      swiper.updateAutoHeight();
    }
    swiper.updateSlidesClasses();
    if (params.effect !== 'slide') {
      swiper.setTranslate(translate);
    }
    return false;
  }

  if (speed === 0 || !Support.transition) {
    swiper.setTransition(0);
    swiper.setTranslate(translate);
    swiper.updateActiveIndex(slideIndex);
    swiper.updateSlidesClasses();
    swiper.emit('beforeTransitionStart', speed, internal);
    swiper.transitionStart(runCallbacks);
    swiper.transitionEnd(runCallbacks);
  } else {
    swiper.setTransition(speed);
    swiper.setTranslate(translate);
    swiper.updateActiveIndex(slideIndex);
    swiper.updateSlidesClasses();
    swiper.emit('beforeTransitionStart', speed, internal);
    swiper.transitionStart(runCallbacks);
    if (!swiper.animating) {
      swiper.animating = true;
      $wrapperEl.transitionEnd(() => {
        if (!swiper || swiper.destroyed) return;
        swiper.transitionEnd(runCallbacks);
      });
    }
  }

  return true;
}
<commit_msg>Fix locks reset<commit_after>import Support from '../../../utils/support';

export default function (index = 0, speed = this.params.speed, runCallbacks = true, internal) {
  const swiper = this;
  let slideIndex = index;
  if (slideIndex < 0) slideIndex = 0;

  const {
    params, snapGrid, slidesGrid, previousIndex, activeIndex, rtl, $wrapperEl,
  } = swiper;

  let snapIndex = Math.floor(slideIndex / params.slidesPerGroup);
  if (snapIndex >= snapGrid.length) snapIndex = snapGrid.length - 1;

  if ((activeIndex || params.initialSlide || 0) === (previousIndex || 0) && runCallbacks) {
    swiper.emit('beforeSlideChangeStart');
  }

  const translate = -snapGrid[snapIndex];

  // Update progress
  swiper.updateProgress(translate);

  // Normalize slideIndex
  if (params.normalizeSlideIndex) {
    for (let i = 0; i < slidesGrid.length; i += 1) {
      if (-Math.floor(translate * 100) >= Math.floor(slidesGrid[i] * 100)) {
        slideIndex = i;
      }
    }
  }
  // Directions locks
  if (swiper.initialized && slideIndex !== activeIndex) {
    if (!swiper.allowSlideNext && translate < swiper.translate && translate < swiper.minTranslate()) {
      return false;
    }
    if (!swiper.allowSlidePrev && translate > swiper.translate && translate > swiper.maxTranslate()) {
      if ((activeIndex || 0) !== slideIndex) return false;
    }
  }

  // Update Index
  if ((rtl && -translate === swiper.translate) || (!rtl && translate === swiper.translate)) {
    swiper.updateActiveIndex(slideIndex);
    // Update Height
    if (params.autoHeight) {
      swiper.updateAutoHeight();
    }
    swiper.updateSlidesClasses();
    if (params.effect !== 'slide') {
      swiper.setTranslate(translate);
    }
    return false;
  }

  if (speed === 0 || !Support.transition) {
    swiper.setTransition(0);
    swiper.setTranslate(translate);
    swiper.updateActiveIndex(slideIndex);
    swiper.updateSlidesClasses();
    swiper.emit('beforeTransitionStart', speed, internal);
    swiper.transitionStart(runCallbacks);
    swiper.transitionEnd(runCallbacks);
  } else {
    swiper.setTransition(speed);
    swiper.setTranslate(translate);
    swiper.updateActiveIndex(slideIndex);
    swiper.updateSlidesClasses();
    swiper.emit('beforeTransitionStart', speed, internal);
    swiper.transitionStart(runCallbacks);
    if (!swiper.animating) {
      swiper.animating = true;
      $wrapperEl.transitionEnd(() => {
        if (!swiper || swiper.destroyed) return;
        swiper.transitionEnd(runCallbacks);
      });
    }
  }

  return true;
}
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import autobind from 'react-autobind';
import classNames from 'classnames';
import enhanceWithClickOutside from 'react-click-outside';

import nodeSpecs from '../../engine/nodes';

import './editor-node.css';

class EditorNode extends Component {
  clickX = 0;
  clickY = 0;

  constructor() {
    super();

    this.inletsDivs = {};

    this.state = { selected: false };

    autobind(this);
  }

  componentDidMount() {
    const { x, y } = this.props;

    const inletsPositions = Object.keys(this.inletsDivs).reduce((memo, key) => {
      const rect = this.inletsDivs[key].getBoundingClientRect();

      return Object.assign(memo, {
        [key]: {
          x: rect.left - x + rect.width / 2,
          y: rect.top  - y
        }
      });
    }, {});

    this.props.setInletsPositions(inletsPositions);

    window.addEventListener('keydown', this.onKeyDown);
  }

  componentWillUnmount() {
    window.removeEventListener('keydown', this.onKeyDown);
  }

  onKeyDown(e) {
    const { key, target } = e;
    const { selected }    = this.state;

    if (selected && key === 'Backspace' && target.localName === 'body') {
      this.props.onDelete();
    }
  }

  onMouseDown(e, callbacks) {
    // adjust for scroll position of editor content
    const boundingRect = this.props.contentRef.getBoundingClientRect();

    this.clickX = e.clientX - this.props.x - boundingRect.left;
    this.clickY = e.clientY - this.props.y - boundingRect.top;

    const onDrag = (e) => {
      callbacks.onDrag({
        relative: {
          x: e.clientX - this.clickX - boundingRect.left,
          y: e.clientY - this.clickY - boundingRect.top
        },
        absolute: {
          x: e.clientX - boundingRect.left,
          y: e.clientY - boundingRect.top
        }
      });
    };

    const onUp = () => {
      window.removeEventListener('mousemove', onDrag);
      window.removeEventListener('mouseup', onUp);

      if (callbacks.onUp) { callbacks.onUp(); }
    };

    window.addEventListener('mousemove', onDrag);
    window.addEventListener('mouseup', onUp);

    this.setState({ selected: true });
  }

  onMouseDownNode(e) {
    this.onMouseDown(e, {
      onDrag: ({ relative }) => { this.props.onMove(relative); }
    });
  }

  onMouseDownOutlet(e, outlet) {
    this.onMouseDown(e, {
      onDrag: ({ absolute }) => { this.props.onDragEdge(absolute, outlet); },
      onUp:   () => { this.props.onDragEdgeDone(); }
    });
  }

  onUpdateMetadata(metadata) {
    this.props.onUpdateMetadata(metadata);
  }

  handleClickOutside() {
    this.setState({ selected: false });
  }

  renderInlets() {
    const { selected } = this.state;
    const { type }     = this.props;
    const { inlets }   = nodeSpecs[type].spec;

    const typeNames = {
      float: 'f',
      vec3:  '3'
    };

    return

      {
        inlets && inlets.map(inlet => {
          return
 this.inletsDivs[inlet.id] = ref }>
            { inlet.id }
            { typeNames[inlet.type] }

        })
      }

;
  }

  renderOutlets() {
    const { selected } = this.state;
    const { type }     = this.props;
    const { outlet }   = nodeSpecs[type].spec;

    const typeNames = {
      float: 'f',
      vec3:  '3'
    };

    return

      {
        outlet &&
 this.onMouseDownOutlet(e, outlet) }>
          { outlet.id }
          { typeNames[outlet.type] }

      }

;
  }

  renderCustomUI() {
    const { metadata, type } = this.props;
    const CustomUI           = nodeSpecs[type].ui;

    return <CustomUI
      metadata={ metadata ? metadata.toJS() : {} }
      onUpdateMetadata={ this.onUpdateMetadata }/>
  }

  render() {
    const { selected }   = this.state;
    const { x, y, type } = this.props;
    const hasCustomUI    = !!nodeSpecs[type].ui;

    return

      { this.renderInlets() }

          { hasCustomUI ? this.renderCustomUI() : type }

      { this.renderOutlets() }

;
  }
}

export default enhanceWithClickOutside(EditorNode);
<commit_msg>fix for connecting nodes if editor scrolls<commit_after>import React, { Component } from 'react';
import autobind from 'react-autobind';
import classNames from 'classnames';
import enhanceWithClickOutside from 'react-click-outside';

import nodeSpecs from '../../engine/nodes';

import './editor-node.css';

class EditorNode extends Component {
  clickX = 0;
  clickY = 0;

  constructor() {
    super();

    this.inletsDivs = {};

    this.state = { selected: false };

    autobind(this);
  }

  componentDidMount() {
    const { x, y } = this.props;

    // adjust for scroll position of editor content
    const boundingRect = this.props.contentRef.getBoundingClientRect();

    const inletsPositions = Object.keys(this.inletsDivs).reduce((memo, key) => {
      const rect = this.inletsDivs[key].getBoundingClientRect();

      return Object.assign(memo, {
        [key]: {
          x: rect.left - boundingRect.left - x + rect.width / 2,
          y: rect.top  - boundingRect.top  - y
        }
      });
    }, {});

    this.props.setInletsPositions(inletsPositions);

    window.addEventListener('keydown', this.onKeyDown);
  }

  componentWillUnmount() {
    window.removeEventListener('keydown', this.onKeyDown);
  }

  onKeyDown(e) {
    const { key, target } = e;
    const { selected }    = this.state;

    if (selected && key === 'Backspace' && target.localName === 'body') {
      this.props.onDelete();
    }
  }

  onMouseDown(e, callbacks) {
    // adjust for scroll position of editor content
    const boundingRect = this.props.contentRef.getBoundingClientRect();

    this.clickX = e.clientX - this.props.x - boundingRect.left;
    this.clickY = e.clientY - this.props.y - boundingRect.top;

    const onDrag = (e) => {
      callbacks.onDrag({
        relative: {
          x: e.clientX - this.clickX - boundingRect.left,
          y: e.clientY - this.clickY - boundingRect.top
        },
        absolute: {
          x: e.clientX - boundingRect.left,
          y: e.clientY - boundingRect.top
        }
      });
    };

    const onUp = () => {
      window.removeEventListener('mousemove', onDrag);
      window.removeEventListener('mouseup', onUp);

      if (callbacks.onUp) { callbacks.onUp(); }
    };

    window.addEventListener('mousemove', onDrag);
    window.addEventListener('mouseup', onUp);

    this.setState({ selected: true });
  }

  onMouseDownNode(e) {
    this.onMouseDown(e, {
      onDrag: ({ relative }) => { this.props.onMove(relative); }
    });
  }

  onMouseDownOutlet(e, outlet) {
    this.onMouseDown(e, {
      onDrag: ({ absolute }) => { this.props.onDragEdge(absolute, outlet); },
      onUp:   () => { this.props.onDragEdgeDone(); }
    });
  }

  onUpdateMetadata(metadata) {
    this.props.onUpdateMetadata(metadata);
  }

  handleClickOutside() {
    this.setState({ selected: false });
  }

  renderInlets() {
    const { selected } = this.state;
    const { type }     = this.props;
    const { inlets }   = nodeSpecs[type].spec;

    const typeNames = {
      float: 'f',
      vec3:  '3'
    };

    return

      {
        inlets && inlets.map(inlet => {
          return
 this.inletsDivs[inlet.id] = ref }>
            { inlet.id }
            { typeNames[inlet.type] }

        })
      }

;
  }

  renderOutlets() {
    const { selected } = this.state;
    const { type }     = this.props;
    const { outlet }   = nodeSpecs[type].spec;

    const typeNames = {
      float: 'f',
      vec3:  '3'
    };

    return

      {
        outlet &&
 this.onMouseDownOutlet(e, outlet) }>
          { outlet.id }
          { typeNames[outlet.type] }

      }

;
  }

  renderCustomUI() {
    const { metadata, type } = this.props;
    const CustomUI           = nodeSpecs[type].ui;

    return <CustomUI
      metadata={ metadata ? metadata.toJS() : {} }
      onUpdateMetadata={ this.onUpdateMetadata }/>
  }

  render() {
    const { selected }   = this.state;
    const { x, y, type } = this.props;
    const hasCustomUI    = !!nodeSpecs[type].ui;

    return

      { this.renderInlets() }

          { hasCustomUI ? this.renderCustomUI() : type }

      { this.renderOutlets() }

;
  }
}

export default enhanceWithClickOutside(EditorNode);
<|endoftext|>