<commit_before>const Team = Object.freeze({Orange: "Orange", Blue: "Blue"});

// XXX hideous; can we put this somewhere else, please?
const initialReplies = [{star: {team: Team.Orange, job_description: "Servant"}, whence: {rank: 1, file: 0}, whither: {rank: 2, file: 0}},  {star: {team: Team.Orange, job_description: "Servant"}, whence: {rank: 1, file: 0}, whither: {rank: 3, file: 0}},  {star: {team: Team.Orange, job_description: "Servant"}, whence: {rank: 1, file: 1}, whither: {rank: 2, file: 1}},  {star: {team: Team.Orange, job_description: "Servant"}, whence: {rank: 1, file: 1}, whither: {rank: 3, file: 1}},  {star: {team: Team.Orange, job_description: "Servant"}, whence: {rank: 1, file: 2}, whither: {rank: 2, file: 2}},  {star: {team: Team.Orange, job_description: "Servant"}, whence: {rank: 1, file: 2}, whither: {rank: 3, file: 2}},  {star: {team: Team.Orange, job_description: "Servant"}, whence: {rank: 1, file: 3}, whither: {rank: 2, file: 3}},  {star: {team: Team.Orange, job_description: "Servant"}, whence: {rank: 1, file: 3}, whither: {rank: 3, file: 3}},  {star: {team: Team.Orange, job_description: "Servant"}, whence: {rank: 1, file: 4}, whither: {rank: 2, file: 4}},  {star: {team: Team.Orange, job_description: "Servant"}, whence: {rank: 1, file: 4}, whither: {rank: 3, file: 4}},  {star: {team: Team.Orange, job_description: "Servant"}, whence: {rank: 1, file: 5}, whither: {rank: 2, file: 5}},  {star: {team: Team.Orange, job_description: "Servant"}, whence: {rank: 1, file: 5}, whither: {rank: 3, file: 5}},  {star: {team: Team.Orange, job_description: "Servant"}, whence: {rank: 1, file: 6}, whither: {rank: 2, file: 6}},  {star: {team: Team.Orange, job_description: "Servant"}, whence: {rank: 1, file: 6}, whither: {rank: 3, file: 6}},  {star: {team: Team.Orange, job_description: "Servant"}, whence: {rank: 1, file: 7}, whither: {rank: 2, file: 7}},  {star: {team: Team.Orange, job_description: "Servant"}, whence: {rank: 1, file: 7}, whither: {rank: 3, file: 7}},  {star: {team: Team.Orange, job_description: "Pony"}, whence: {rank: 0, file: 1}, whither: {rank: 2, file: 0}},  {star: {team: Team.Orange, job_description: "Pony"}, whence: {rank: 0, file: 1}, whither: {rank: 2, file: 2}},  {star: {team: Team.Orange, job_description: "Pony"}, whence: {rank: 0, file: 6}, whither: {rank: 2, file: 5}}, {star: {team: Team.Orange, job_description: "Pony"}, whence: {rank: 0, file: 6}, whither: {rank: 2, file: 7}}];

const configuration = {
    position: 'start',
    draggable: true,
    pieceTheme: 'img/figurines/{piece}.png',
    onDrop: dropHandler,
};

class WorldState {
    constructor() {
        this.initiative = Team.Orange;
        this.preservedServiceEligibilities = "KQkq";
        this.multifield = ChessBoard('world', configuration);
        this.replies = initialReplies;
    }

    preserveInitiative() {
        if (this.initiative === Team.Orange) {
            return 'w';
        } else if (this.initiative === Team.Blue) {
            return 'b';
        }
    }

    preserve() {
        return `${this.multifield.fen()} ` +
            `${this.preserveInitiative()} ` +
            `${this.preservedServiceEligibilities}`;
    }

    cedeInitiative() {
        this.initiative = (this.initiative === Team.Orange) ? Team.Blue : Team.Orange;
    }

    loadReplies(replies) {
        this.replies = replies;
    }

    validateMovement(movement) {
        let validations = (for (reply of this.replies)
            (_.isEqual(movement, reply)));
        for (var validity of validations) {
            if (validity) {
                return true;
            }
        }
        return false;
    }
}

let world = new WorldState();

const $history = $('#history');
const $message = $('#message');
const $spinner = $('#spinner');

const $ascensionModal = $('#ascension-modal')
const $ponyAscensionButton = $('#pony-ascension-button');
const $scholarAscensionButton = $('#scholar-ascension-button');
const $copAscensionButton = $('#cop-ascension-button');
const $princessAscensionButton = $('#princess-ascension-button');
let pendingAscension = null;

function leaflineAgentToGuiAgentRune(agent) {
    let teamToPrefix = {'Orange': "w", 'Blue': "b"};
    let jobDescriptionToTail = {
        'Servant': "P",
        'Pony': "N",
        'Scholar': "B",
        'Cop': "R",
        'Princess': "Q",
        'Figurehead': "K",
    };
    return (teamToPrefix[agent.team] +
            jobDescriptionToTail[agent.job_description]);
}

function guiAgentRuneToLeaflineAgent(rune) {
    let prefixToTeam = {'w': Team.Orange, 'b': Team.Blue};
    let tailToJobDescription = {
        'P': "Servant",
        'N': "Pony",
        'B': "Scholar",
        'R': "Cop",
        'Q': "Princess",
        'K': "Figurehead",
    };
    let [prefix, tail] = rune;
    return {'team': prefixToTeam[prefix],
            'job_description': tailToJobDescription[tail]};
}

function teamAndOppositionFromPrefix(prefix) {
    if (prefix === 'w') {
        return [Team.Orange, Team.Blue];
    } else if (prefix === 'b') {
        return [Team.Blue, Team.Orange];
    }
}

function localeToAlgebraic(locale) {
    let rank = String.fromCharCode(locale.rank + 49);  // 49 == '1'
    let file = String.fromCharCode(locale.file + 97);  // 97 == 'a'
    return file + rank;
}

function algebraicToLocale(algebraic) {
    let [fileIndicator, rankIndicator] = algebraic;
    return {rank: rankIndicator.charCodeAt() - 49,
            file: fileIndicator.charCodeAt() - 97};
}

function getLookaheadBound() {
    let nature;
    if ($('#depth-radio-button').is(':checked')) {
        nature = "depth";
    } else if ($('#seconds-radio-button').is(':checked')) {
        nature = "seconds";
    } else {
        throw "need to select a lookahead bound–nature"
    }
    let value = parseInt($('#bound-input').val());
    return { nature: nature, value: value }
}

function sendPostcard(news) {
    $message.text('');
    $.ajax({
        url: "/write/",
        method: 'POST',
        data: {
            world: world.preserve(),
            bound: getLookaheadBound()
        },
        success: function (missive, textStatus, jqxhr) {
            let [newField, _initiative,
                 newEligibilities] = missive.world.split(/ /);
            world.cedeInitiative();
            world.multifield.position(newField);
            world.preservedServiceEligibilities = newEligibilities;
            world.replies = missive.replies;
            let commentary = ` (after searching ${missive.depth} plies in ` +
                             `${missive.thinking_time} ms)`;
            $spinner.hide();
            printHeadline(
                "Blue",
                missive.patch.star,
                localeToAlgebraic(missive.patch.whence),
                localeToAlgebraic(missive.patch.whither),
                missive.hospitalization,
                commentary
            );
            transpireYear();
        },
        error: function (jqxhr, textStatus, errorThrown) {
            $spinner.hide();
            $message.text(errorThrown);
        }
    });
}

function dropHandler(whence, whither, agentRune,
                     news, previously, _orientation) {
    if (whence != whither && whither != 'offboard') {
        let [team, _opposition] = teamAndOppositionFromPrefix(agentRune[0]);
        let agent = guiAgentRuneToLeaflineAgent(agentRune);
        let movement = {star: agent,
                        whence: algebraicToLocale(whence),
                        whither: algebraicToLocale(whither)};
        if (!world.validateMovement(movement)) {
            return "snapback";
        }
        if (agent.job_description == "Servant" && movement.whither.rank == 7) {
            // ascension
            $ascensionModal.foundation('reveal', "open");
            pendingAscension = movement;
            return "snapback";
        }
        let occupyingWhither = previously[whither];
        let patient;
        if (occupyingWhither) {
            patient = guiAgentRuneToLeaflineAgent(occupyingWhither)
        } else {
            patient = null;
        }
        world.multifield.position(news, false);
        world.cedeInitiative();
        sendPostcard(news);
        $spinner.show();
        printHeadline(
            "Orange", guiAgentRuneToLeaflineAgent(agentRune),
            whence, whither, patient, null
        );
    }
}

function getYear() {
    return parseInt($history.attr('data-year'));
}

function transpireYear() {
    $history.attr('data-year', getYear() + 1);
}

function guiAgentRuneToIcon(agentRune) {
    return $('<img>').attr('src', `img/figurines/${agentRune}.png`)
        .addClass('agent-icon');
}

function leaflineAgentToIcon(agent) {
    return guiAgentRuneToIcon(leaflineAgentToGuiAgentRune(agent));
}

function printHeadline(team, agent, whence, whither,
                       hospitalization, commentary) {
    let year = getYear();
    let $headline = $('<div />').addClass("headline")
        .attr('data-team', team).attr('data-year', year);
    let dateline;
    if (team === "Orange") {
        dateline = year + ". ";
    } else if (team === "Blue") {
        dateline = ".. ";
    }
    $headline.append($('<strong />').text(dateline));

    $headline.append(leaflineAgentToIcon(agent));
    let newsEvent = ` from ${whence} to ${whither}`;
    $headline.append($('<span />').text(newsEvent));

    if (hospitalization) {
        let hospitalMugshot = leaflineAgentToIcon(hospitalization);
        $headline.append(
            $('<span />').text(`, stunning `)
        );
        $headline.append(hospitalMugshot);
    }

    if (commentary) {
        $headline.append($('<em />').text(commentary).addClass("commentary"));
    }

    $history.append($headline);
}

$(document).ready(function() {
    $('#ascension-modal-close-control').on('click', function (_event) {
        pendingAscension = null;
    });
    $('.ascension-button').on('click', function (_event) {
        // XXX factorize
        if (world.validateMovement(pendingAscension)) {
            $ascensionModal.foundation('reveal', "close");
            let ascendedFormRune = `w${$(this).data('job-description-rune')}`;
            window.setTimeout(function () {
                let news = world.multifield.position();
                let algebraicWhence = localeToAlgebraic(pendingAscension.whence);
                let algebraicWhither = localeToAlgebraic(
                    pendingAscension.whither);
                delete news[algebraicWhence];
                news[algebraicWhither] = ascendedFormRune;
                world.multifield.position(news);
                world.cedeInitiative();
                printHeadline(
                    "Orange", guiAgentRuneToLeaflineAgent(ascendedFormRune),
                    algebraicWhence, algebraicWhither,
                    null, // XXX need to take stunning ascension into account
                    " (servant ascension)"
                );
                pendingAscension = null;
                sendPostcard(news);
                $spinner.show();
            }, 500);
        } else {
            alert("No Pending Ascension Error");  // yeah, that just happened
        }
    });
});
<commit_msg>simple secret service UI for web client play<commit_after>const Team = Object.freeze({Orange: "Orange", Blue: "Blue"});

// XXX hideous; can we put this somewhere else, please?
const initialReplies = [{star: {team: Team.Orange, job_description: "Servant"}, whence: {rank: 1, file: 0}, whither: {rank: 2, file: 0}},  {star: {team: Team.Orange, job_description: "Servant"}, whence: {rank: 1, file: 0}, whither: {rank: 3, file: 0}},  {star: {team: Team.Orange, job_description: "Servant"}, whence: {rank: 1, file: 1}, whither: {rank: 2, file: 1}},  {star: {team: Team.Orange, job_description: "Servant"}, whence: {rank: 1, file: 1}, whither: {rank: 3, file: 1}},  {star: {team: Team.Orange, job_description: "Servant"}, whence: {rank: 1, file: 2}, whither: {rank: 2, file: 2}},  {star: {team: Team.Orange, job_description: "Servant"}, whence: {rank: 1, file: 2}, whither: {rank: 3, file: 2}},  {star: {team: Team.Orange, job_description: "Servant"}, whence: {rank: 1, file: 3}, whither: {rank: 2, file: 3}},  {star: {team: Team.Orange, job_description: "Servant"}, whence: {rank: 1, file: 3}, whither: {rank: 3, file: 3}},  {star: {team: Team.Orange, job_description: "Servant"}, whence: {rank: 1, file: 4}, whither: {rank: 2, file: 4}},  {star: {team: Team.Orange, job_description: "Servant"}, whence: {rank: 1, file: 4}, whither: {rank: 3, file: 4}},  {star: {team: Team.Orange, job_description: "Servant"}, whence: {rank: 1, file: 5}, whither: {rank: 2, file: 5}},  {star: {team: Team.Orange, job_description: "Servant"}, whence: {rank: 1, file: 5}, whither: {rank: 3, file: 5}},  {star: {team: Team.Orange, job_description: "Servant"}, whence: {rank: 1, file: 6}, whither: {rank: 2, file: 6}},  {star: {team: Team.Orange, job_description: "Servant"}, whence: {rank: 1, file: 6}, whither: {rank: 3, file: 6}},  {star: {team: Team.Orange, job_description: "Servant"}, whence: {rank: 1, file: 7}, whither: {rank: 2, file: 7}},  {star: {team: Team.Orange, job_description: "Servant"}, whence: {rank: 1, file: 7}, whither: {rank: 3, file: 7}},  {star: {team: Team.Orange, job_description: "Pony"}, whence: {rank: 0, file: 1}, whither: {rank: 2, file: 0}},  {star: {team: Team.Orange, job_description: "Pony"}, whence: {rank: 0, file: 1}, whither: {rank: 2, file: 2}},  {star: {team: Team.Orange, job_description: "Pony"}, whence: {rank: 0, file: 6}, whither: {rank: 2, file: 5}}, {star: {team: Team.Orange, job_description: "Pony"}, whence: {rank: 0, file: 6}, whither: {rank: 2, file: 7}}];

const configuration = {
    position: 'start',
    draggable: true,
    pieceTheme: 'img/figurines/{piece}.png',
    onDrop: dropHandler,
};

class WorldState {
    constructor() {
        this.initiative = Team.Orange;
        this.preservedServiceEligibilities = "KQkq";
        this.multifield = ChessBoard('world', configuration);
        this.replies = initialReplies;
    }

    preserveInitiative() {
        if (this.initiative === Team.Orange) {
            return 'w';
        } else if (this.initiative === Team.Blue) {
            return 'b';
        }
    }

    preserve() {
        return `${this.multifield.fen()} ` +
            `${this.preserveInitiative()} ` +
            `${this.preservedServiceEligibilities}`;
    }

    cedeInitiative() {
        this.initiative = (this.initiative === Team.Orange) ? Team.Blue : Team.Orange;
    }

    loadReplies(replies) {
        this.replies = replies;
    }

    validateMovement(movement) {
        let validations = (for (reply of this.replies)
            (_.isEqual(movement, reply)));
        for (var validity of validations) {
            if (validity) {
                return true;
            }
        }
        return false;
    }
}

let world = new WorldState();

const $history = $('#history');
const $message = $('#message');
const $spinner = $('#spinner');

const $ascensionModal = $('#ascension-modal')
const $ponyAscensionButton = $('#pony-ascension-button');
const $scholarAscensionButton = $('#scholar-ascension-button');
const $copAscensionButton = $('#cop-ascension-button');
const $princessAscensionButton = $('#princess-ascension-button');
let pendingAscension = null;

function leaflineAgentToGuiAgentRune(agent) {
    let teamToPrefix = {'Orange': "w", 'Blue': "b"};
    let jobDescriptionToTail = {
        'Servant': "P",
        'Pony': "N",
        'Scholar': "B",
        'Cop': "R",
        'Princess': "Q",
        'Figurehead': "K",
    };
    return (teamToPrefix[agent.team] +
            jobDescriptionToTail[agent.job_description]);
}

function guiAgentRuneToLeaflineAgent(rune) {
    let prefixToTeam = {'w': Team.Orange, 'b': Team.Blue};
    let tailToJobDescription = {
        'P': "Servant",
        'N': "Pony",
        'B': "Scholar",
        'R': "Cop",
        'Q': "Princess",
        'K': "Figurehead",
    };
    let [prefix, tail] = rune;
    return {'team': prefixToTeam[prefix],
            'job_description': tailToJobDescription[tail]};
}

function teamAndOppositionFromPrefix(prefix) {
    if (prefix === 'w') {
        return [Team.Orange, Team.Blue];
    } else if (prefix === 'b') {
        return [Team.Blue, Team.Orange];
    }
}

function localeToAlgebraic(locale) {
    let rank = String.fromCharCode(locale.rank + 49);  // 49 == '1'
    let file = String.fromCharCode(locale.file + 97);  // 97 == 'a'
    return file + rank;
}

function algebraicToLocale(algebraic) {
    let [fileIndicator, rankIndicator] = algebraic;
    return {rank: rankIndicator.charCodeAt() - 49,
            file: fileIndicator.charCodeAt() - 97};
}

function getLookaheadBound() {
    let nature;
    if ($('#depth-radio-button').is(':checked')) {
        nature = "depth";
    } else if ($('#seconds-radio-button').is(':checked')) {
        nature = "seconds";
    } else {
        throw "need to select a lookahead bound–nature"
    }
    let value = parseInt($('#bound-input').val());
    return { nature: nature, value: value }
}

function sendPostcard(news) {
    $message.text('');
    $.ajax({
        url: "/write/",
        method: 'POST',
        data: {
            world: world.preserve(),
            bound: getLookaheadBound()
        },
        success: function (missive, textStatus, jqxhr) {
            let [newField, _initiative,
                 newEligibilities] = missive.world.split(/ /);
            world.cedeInitiative();
            world.multifield.position(newField);
            world.preservedServiceEligibilities = newEligibilities;
            world.replies = missive.replies;
            let commentary = ` (after searching ${missive.depth} plies in ` +
                             `${missive.thinking_time} ms)`;
            $spinner.hide();
            printHeadline(
                "Blue",
                missive.patch.star,
                localeToAlgebraic(missive.patch.whence),
                localeToAlgebraic(missive.patch.whither),
                missive.hospitalization,
                commentary
            );
            transpireYear();
        },
        error: function (jqxhr, textStatus, errorThrown) {
            $spinner.hide();
            $message.text(errorThrown);
        }
    });
}

function dropHandler(whence, whither, agentRune,
                     news, previously, _orientation) {
    if (whence != whither && whither != 'offboard') {
        let [team, _opposition] = teamAndOppositionFromPrefix(agentRune[0]);
        let agent = guiAgentRuneToLeaflineAgent(agentRune);
        let movement = {star: agent,
                        whence: algebraicToLocale(whence),
                        whither: algebraicToLocale(whither)};
        if (!world.validateMovement(movement)) {
            return "snapback";
        }
        if (agent.job_description === "Servant" && movement.whither.rank === 7) {
            // ascension
            $ascensionModal.foundation('reveal', "open");
            pendingAscension = movement;
            return "snapback";
        }
        if (agent.job_description === "Figurehead" &&
            Math.abs(movement.whence.file - movement.whither.file) === 2) {
            // secret service
            //
            // XXX TODO (here as elsewhere): eventually we might want to
            // drop the assumption that the human controls the Orange
            // Team
            let longitude = movement.whither.file - movement.whence.file;
            if (longitude === 2) {  // east service
                delete news['h1'];
                news.f1 = "wR";
            } else if (longitude === -2) {  // west service
                delete news['a1'];
                news.d1 = "wR";
            }
        }
        let occupyingWhither = previously[whither];
        let patient;
        if (occupyingWhither) {
            patient = guiAgentRuneToLeaflineAgent(occupyingWhither)
        } else {
            patient = null;
        }
        world.multifield.position(news, false);
        world.cedeInitiative();
        sendPostcard(news);
        $spinner.show();
        printHeadline(
            "Orange", guiAgentRuneToLeaflineAgent(agentRune),
            whence, whither, patient, null
        );
    }
}

function getYear() {
    return parseInt($history.attr('data-year'));
}

function transpireYear() {
    $history.attr('data-year', getYear() + 1);
}

function guiAgentRuneToIcon(agentRune) {
    return $('<img>').attr('src', `img/figurines/${agentRune}.png`)
        .addClass('agent-icon');
}

function leaflineAgentToIcon(agent) {
    return guiAgentRuneToIcon(leaflineAgentToGuiAgentRune(agent));
}

function printHeadline(team, agent, whence, whither,
                       hospitalization, commentary) {
    let year = getYear();
    let $headline = $('<div />').addClass("headline")
        .attr('data-team', team).attr('data-year', year);
    let dateline;
    if (team === "Orange") {
        dateline = year + ". ";
    } else if (team === "Blue") {
        dateline = ".. ";
    }
    $headline.append($('<strong />').text(dateline));

    $headline.append(leaflineAgentToIcon(agent));
    let newsEvent = ` from ${whence} to ${whither}`;
    $headline.append($('<span />').text(newsEvent));

    if (hospitalization) {
        let hospitalMugshot = leaflineAgentToIcon(hospitalization);
        $headline.append(
            $('<span />').text(`, stunning `)
        );
        $headline.append(hospitalMugshot);
    }

    if (commentary) {
        $headline.append($('<em />').text(commentary).addClass("commentary"));
    }

    $history.append($headline);
}

$(document).ready(function() {
    $('#ascension-modal-close-control').on('click', function (_event) {
        pendingAscension = null;
    });
    $('.ascension-button').on('click', function (_event) {
        // XXX factorize
        if (world.validateMovement(pendingAscension)) {
            $ascensionModal.foundation('reveal', "close");
            let ascendedFormRune = `w${$(this).data('job-description-rune')}`;
            window.setTimeout(function () {
                let news = world.multifield.position();
                let algebraicWhence = localeToAlgebraic(pendingAscension.whence);
                let algebraicWhither = localeToAlgebraic(
                    pendingAscension.whither);
                delete news[algebraicWhence];
                news[algebraicWhither] = ascendedFormRune;
                world.multifield.position(news);
                world.cedeInitiative();
                printHeadline(
                    "Orange", guiAgentRuneToLeaflineAgent(ascendedFormRune),
                    algebraicWhence, algebraicWhither,
                    null, // XXX need to take stunning ascension into account
                    " (servant ascension)"
                );
                pendingAscension = null;
                sendPostcard(news);
                $spinner.show();
            }, 500);
        } else {
            alert("No Pending Ascension Error");  // yeah, that just happened
        }
    });
});
<|endoftext|>


<commit_before>import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import isEqual from 'lodash.isequal'
import { Route, Switch, withRouter } from 'react-router'
import { createHashHistory } from 'history'
import { ConnectedRouter } from 'connected-react-router'

import PrintLayout from './print-layout'
import { setMapCenter, setMapZoom } from '../../actions/config'
import { setLocationToCurrent } from '../../actions/map'
import { getCurrentPosition, receivedPositionResponse } from '../../actions/location'
import { formChanged, parseUrlQueryString } from '../../actions/form'
import { handleBackButtonPress, matchContentToUrl } from '../../actions/ui'
import { getUrlParams } from '../../util/query'
import { getTitle, isMobile } from '../../util/ui'
import { getActiveItinerary } from '../../util/state'

class ResponsiveWebapp extends Component {
  static propTypes = {
    desktopView: PropTypes.element,
    initZoomOnLocate: PropTypes.number,
    mobileView: PropTypes.element,
    query: PropTypes.object
  }

  /** Lifecycle methods **/

  componentDidUpdate (prevProps) {
    const { currentPosition, location, query, title } = this.props
    document.title = title
    const urlParams = getUrlParams()
    const newSearchId = urlParams.ui_activeSearch
    const replanningTrip = newSearchId && this.props.activeSearchId && newSearchId !== this.props.activeSearchId
    if (!isEqual(prevProps.query, query) && !replanningTrip) {
      // Trigger on form change action if previous query is different from
      // current one AND . This will determine whether a search needs to be made, the
      // mobile view needs updating, etc.
      this.props.formChanged(prevProps.query, query)
    }

    // check if device position changed (typically only set once, on initial page load)
    if (currentPosition !== prevProps.currentPosition) {
      if (currentPosition.error || !currentPosition.coords) return
      const pt = {
        lat: currentPosition.coords.latitude,
        lon: currentPosition.coords.longitude
      }

      // if in mobile mode and from field is not set, use current location as from and recenter map
      if (isMobile() && this.props.query.from === null) {
        this.props.setLocationToCurrent({ type: 'from' })
        this.props.setMapCenter(pt)
        if (this.props.initZoomOnLocate) {
          this.props.setMapZoom({ zoom: this.props.initZoomOnLocate })
        }
      }
    }
    // If the path changes (e.g., via a back button press) check whether the
    // main content needs to switch between, for example, a viewer and a search.
    if (!isEqual(location.pathname, prevProps.location.pathname)) {
      // console.log('url changed to', location.pathname)
      this.props.matchContentToUrl(location)
    }
    // Check for change between ITINERARY and PROFILE routingTypes
    // TODO: restore this for profile mode
    /* if (query.routingType !== nextProps.query.routingType) {
      let queryModes = nextProps.query.mode.split(',')
      // If we are entering 'ITINERARY' mode, ensure that one and only one access mode is selected
      if (nextProps.query.routingType === 'ITINERARY') {
        queryModes = ensureSingleAccessMode(queryModes)
        this.props.setQueryParam({ mode: queryModes.join(',') })
      }
      // If we are entering 'PROFILE' mode, ensure that CAR_HAIL is not selected
      // TODO: make this more generic, i.e. introduce concept of mode->routingType permissions
      if (nextProps.query.routingType === 'ITINERARY') {
        queryModes = queryModes.filter(mode => mode !== 'CAR_HAIL')
        this.props.setQueryParam({ mode: queryModes.join(',') })
      }
    } */
  }

  componentDidMount () {
    // Add on back button press behavior.
    window.addEventListener('popstate', this.props.handleBackButtonPress)
    const { location, title } = this.props
    document.title = title
    if (isMobile()) {
      // If on mobile browser, check position on load
      this.props.getCurrentPosition()

      // Also, watch for changes in position on mobile
      navigator.geolocation.watchPosition(
        // On success
        position => { this.props.receivedPositionResponse({ position }) },
        // On error
        error => { console.log('error in watchPosition', error) },
        // Options
        { enableHighAccuracy: true }
      )
    }

    if (location && location.search) {
      // Set search params and plan trip if routing enabled and a query exists
      // in the URL.
      this.props.parseUrlQueryString()
    }
    // Handle routing to a specific part of the app (e.g. stop viewer) on page
    // load.
    this.props.matchContentToUrl(this.props.location)
  }

  componentWillUnmount () {
    // Remove on back button press listener.
    window.removeEventListener('popstate')
  }

  render () {
    const { desktopView, mobileView } = this.props
    return isMobile() ? mobileView : desktopView
  }
}

// connect to the redux store

const mapStateToProps = (state, ownProps) => {
  const title = getTitle(state)
  return {
    activeItinerary: getActiveItinerary(state.otp),
    activeSearchId: state.otp.activeSearchId,
    currentPosition: state.otp.location.currentPosition,
    query: state.otp.currentQuery,
    searches: state.otp.searches,
    mobileScreen: state.otp.ui.mobileScreen,
    initZoomOnLocate: state.otp.config.map && state.otp.config.map.initZoomOnLocate,
    modeGroups: state.otp.config.modeGroups,
    title
  }
}

const mapDispatchToProps = {
  formChanged,
  getCurrentPosition,
  handleBackButtonPress,
  matchContentToUrl,
  parseUrlQueryString,
  receivedPositionResponse,
  setLocationToCurrent,
  setMapCenter,
  setMapZoom
}

const history = createHashHistory()

const WebappWithRouter = withRouter(
  connect(mapStateToProps, mapDispatchToProps)(ResponsiveWebapp)
)

class RouterWrapper extends Component {
  render () {
    const { routerConfig } = this.props
    return (
      <ConnectedRouter
        basename={routerConfig && routerConfig.basename}
        history={history}>

          <Switch>
            <Route
              exact
              path={[
                '/',
                '/route',
                '/route/:id',
                '/stop',
                '/stop/:id'
              ]}
              render={() => <WebappWithRouter {...this.props} />}
            />
            <Route
              path='/print'
              component={PrintLayout}
            />
            {/*
            <Route
              path='/itin/:id'
              component={mainView}
            />
            */}
            {/* Single route with multiple options or multiple separate routes? */}
            {
              /* TODO: Keep track of application state and handle starting conditions
               * (position and/or OTP router to use)
              <Route
                path='/@:latLonZoom(/plan)'
                component={props => <OtpApp {...props}><OtpRRExample /></OtpApp>} />
              <Route
                path='/plan'
                component={props => <OtpApp {...props}><OtpRRExample /></OtpApp>} />
              */
            }
          </Switch>

      </ConnectedRouter>
    )
  }
}

export default connect((state, ownProps) => {
  return { routerConfig: state.otp.config.reactRouter }
})(RouterWrapper)
<commit_msg>refactor: remove unused react-router paths<commit_after>import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import isEqual from 'lodash.isequal'
import { Route, Switch, withRouter } from 'react-router'
import { createHashHistory } from 'history'
import { ConnectedRouter } from 'connected-react-router'

import PrintLayout from './print-layout'
import { setMapCenter, setMapZoom } from '../../actions/config'
import { setLocationToCurrent } from '../../actions/map'
import { getCurrentPosition, receivedPositionResponse } from '../../actions/location'
import { formChanged, parseUrlQueryString } from '../../actions/form'
import { handleBackButtonPress, matchContentToUrl } from '../../actions/ui'
import { getUrlParams } from '../../util/query'
import { getTitle, isMobile } from '../../util/ui'
import { getActiveItinerary } from '../../util/state'

class ResponsiveWebapp extends Component {
  static propTypes = {
    desktopView: PropTypes.element,
    initZoomOnLocate: PropTypes.number,
    mobileView: PropTypes.element,
    query: PropTypes.object
  }

  /** Lifecycle methods **/

  componentDidUpdate (prevProps) {
    const { currentPosition, location, query, title } = this.props
    document.title = title
    const urlParams = getUrlParams()
    const newSearchId = urlParams.ui_activeSearch
    const replanningTrip = newSearchId && this.props.activeSearchId && newSearchId !== this.props.activeSearchId
    if (!isEqual(prevProps.query, query) && !replanningTrip) {
      // Trigger on form change action if previous query is different from
      // current one AND . This will determine whether a search needs to be made, the
      // mobile view needs updating, etc.
      this.props.formChanged(prevProps.query, query)
    }

    // check if device position changed (typically only set once, on initial page load)
    if (currentPosition !== prevProps.currentPosition) {
      if (currentPosition.error || !currentPosition.coords) return
      const pt = {
        lat: currentPosition.coords.latitude,
        lon: currentPosition.coords.longitude
      }

      // if in mobile mode and from field is not set, use current location as from and recenter map
      if (isMobile() && this.props.query.from === null) {
        this.props.setLocationToCurrent({ type: 'from' })
        this.props.setMapCenter(pt)
        if (this.props.initZoomOnLocate) {
          this.props.setMapZoom({ zoom: this.props.initZoomOnLocate })
        }
      }
    }
    // If the path changes (e.g., via a back button press) check whether the
    // main content needs to switch between, for example, a viewer and a search.
    if (!isEqual(location.pathname, prevProps.location.pathname)) {
      // console.log('url changed to', location.pathname)
      this.props.matchContentToUrl(location)
    }
    // Check for change between ITINERARY and PROFILE routingTypes
    // TODO: restore this for profile mode
    /* if (query.routingType !== nextProps.query.routingType) {
      let queryModes = nextProps.query.mode.split(',')
      // If we are entering 'ITINERARY' mode, ensure that one and only one access mode is selected
      if (nextProps.query.routingType === 'ITINERARY') {
        queryModes = ensureSingleAccessMode(queryModes)
        this.props.setQueryParam({ mode: queryModes.join(',') })
      }
      // If we are entering 'PROFILE' mode, ensure that CAR_HAIL is not selected
      // TODO: make this more generic, i.e. introduce concept of mode->routingType permissions
      if (nextProps.query.routingType === 'ITINERARY') {
        queryModes = queryModes.filter(mode => mode !== 'CAR_HAIL')
        this.props.setQueryParam({ mode: queryModes.join(',') })
      }
    } */
  }

  componentDidMount () {
    // Add on back button press behavior.
    window.addEventListener('popstate', this.props.handleBackButtonPress)
    const { location, title } = this.props
    document.title = title
    if (isMobile()) {
      // If on mobile browser, check position on load
      this.props.getCurrentPosition()

      // Also, watch for changes in position on mobile
      navigator.geolocation.watchPosition(
        // On success
        position => { this.props.receivedPositionResponse({ position }) },
        // On error
        error => { console.log('error in watchPosition', error) },
        // Options
        { enableHighAccuracy: true }
      )
    }

    if (location && location.search) {
      // Set search params and plan trip if routing enabled and a query exists
      // in the URL.
      this.props.parseUrlQueryString()
    }
    // Handle routing to a specific part of the app (e.g. stop viewer) on page
    // load.
    this.props.matchContentToUrl(this.props.location)
  }

  componentWillUnmount () {
    // Remove on back button press listener.
    window.removeEventListener('popstate')
  }

  render () {
    const { desktopView, mobileView } = this.props
    return isMobile() ? mobileView : desktopView
  }
}

// connect to the redux store

const mapStateToProps = (state, ownProps) => {
  const title = getTitle(state)
  return {
    activeItinerary: getActiveItinerary(state.otp),
    activeSearchId: state.otp.activeSearchId,
    currentPosition: state.otp.location.currentPosition,
    query: state.otp.currentQuery,
    searches: state.otp.searches,
    mobileScreen: state.otp.ui.mobileScreen,
    initZoomOnLocate: state.otp.config.map && state.otp.config.map.initZoomOnLocate,
    modeGroups: state.otp.config.modeGroups,
    title
  }
}

const mapDispatchToProps = {
  formChanged,
  getCurrentPosition,
  handleBackButtonPress,
  matchContentToUrl,
  parseUrlQueryString,
  receivedPositionResponse,
  setLocationToCurrent,
  setMapCenter,
  setMapZoom
}

const history = createHashHistory()

const WebappWithRouter = withRouter(
  connect(mapStateToProps, mapDispatchToProps)(ResponsiveWebapp)
)

class RouterWrapper extends Component {
  render () {
    const { routerConfig } = this.props
    return (
      <ConnectedRouter
        basename={routerConfig && routerConfig.basename}
        history={history}>

          <Switch>
            <Route
              exact
              path={[
                '/',
                '/route',
                '/route/:id',
                '/stop',
                '/stop/:id'
              ]}
              render={() => <WebappWithRouter {...this.props} />}
            />
            <Route
              path='/print'
              component={PrintLayout}
            />
            {/* Single route with multiple options or multiple separate routes? */}
            {
              /* TODO: Keep track of application state and handle starting conditions
               * (position and/or OTP router to use)
              <Route
                path='/@:latLonZoom(/plan)'
                component={props => <OtpApp {...props}><OtpRRExample /></OtpApp>} />
              */
            }
          </Switch>

      </ConnectedRouter>
    )
  }
}

export default connect((state, ownProps) => {
  return { routerConfig: state.otp.config.reactRouter }
})(RouterWrapper)
<|endoftext|>


<commit_before>/*
	MIT License http://www.opensource.org/licenses/mit-license.php
	Author Sergey Melyukov @smelukov
*/

"use strict";

const browserslist = require("browserslist");
const path = require("path");

/** @typedef {import("./target").ApiTargetProperties} ApiTargetProperties */
/** @typedef {import("./target").EcmaTargetProperties} EcmaTargetProperties */
/** @typedef {import("./target").PlatformTargetProperties} PlatformTargetProperties */

// [[C:]/path/to/config][:env]
const inputRx = /^(?:((?:[A-Z]:)?[/\\].*?))?(?::(.+?))?$/i;

/**
 * @typedef {Object} BrowserslistHandlerConfig
 * @property {string} [configPath]
 * @property {string} [env]
 * @property {string} [query]
 */

/**
 * @param {BrowserslistHandlerConfig | null} handlerConfig config
 * @returns {EcmaTargetProperties & PlatformTargetProperties & ApiTargetProperties} target properties
 */
const resolve = handlerConfig => {
	const { configPath, env, query } = handlerConfig || {};

	// if a query is specified, then use it, else
	// if a path to a config is specified then load it, else
	// find a nearest config
	const config = query
		? query
		: configPath
		? browserslist.loadConfig({
				config: configPath,
				env
		  })
		: browserslist.loadConfig({ path: process.cwd(), env });

	const browsers = browserslist(config);

	return resolveESFeatures(browsers);
};

/**
 * @param {string} input input string
 * @returns {BrowserslistHandlerConfig|null} config
 */
const parse = input => {
	if (!input) {
		return null;
	}

	if (path.isAbsolute(input)) {
		const [, configPath, env] = inputRx.exec(input) || [];
		return { configPath, env };
	}

	const config = browserslist.findConfig(process.cwd());

	if (config && Object.keys(config).includes(input)) {
		return { env: input };
	}

	return { query: input };
};

/**
 * @param {string[]} browsers supported browsers list
 * @returns {EcmaTargetProperties & PlatformTargetProperties & ApiTargetProperties} target properties
 */
const resolveESFeatures = browsers => {
	/**
	 * Checks only browser against the browserslist feature query
	 * @param {string} feature an ES feature to test
	 * @returns {boolean} true if supports
	 */
	const browserslistChecker = feature => {
		const supportsFeature = browserslist(`supports ${feature}`);
		return browsers.every(v => /^node /.test(v) || supportsFeature.includes(v));
	};
	/**
	 * Checks only node.js version against a version
	 * @param {number} major major version
	 * @param {number} minor minor version
	 * @returns {boolean} true if supports
	 */
	const nodeChecker = (major, minor = 0) => {
		return browsers.every(v => {
			const match = /^node (\d+)(?:\.\d+)?/.exec(v);
			if (!match) return true;
			const [, v1, v2] = match;
			return major === +v1 ? +v2 >= minor : +v1 > major;
		});
	};
	/**
	 * Checks all against a version number
	 * @param {Record<string, number | [number, number]} versions first supported version
	 * @returns {boolean} true if supports
	 */
	const rawChecker = versions => {
		return browsers.every(v => {
			const match = /^([^ ]+) (\d+)(?:\.\d+)?/.exec(v);
			if (!match) return false;
			const [, name, major, minor] = match;
			const version = versions[name];
			if (!version) return false;
			if (typeof version === "number") return +major >= version;
			return version[0] === +major ? +minor >= version[1] : +major > version[0];
		});
	};
	const anyNode = browsers.some(b => /^node /.test(b));
	const anyBrowser = browsers.some(b => /^(?!node)/.test(b));
	const browserProperty = !anyBrowser ? false : anyNode ? null : true;
	const nodeProperty = !anyNode ? false : anyBrowser ? null : true;
	const es6 = browserslistChecker("es6");
	const es6DynamicImport = browserslistChecker("es6-module-dynamic-import");
	const node6 = nodeChecker(6);
	return {
		const: es6 && node6,
		arrowFunction: es6 && node6,
		forOf: es6 && nodeChecker(5),
		destructuring: es6 && node6,
		bigIntLiteral: browserslistChecker("bigint") && nodeChecker(10, 4),
		module: browserslistChecker("es6-module") && nodeChecker(12, 17),
		dynamicImport: es6DynamicImport && nodeChecker(10, 17),
		dynamicImportInWorker: es6DynamicImport && nodeChecker(Infinity),
		// browserslist does not have info about globalThis
		// so this is based on mdn-browser-compat-data
		globalThis: rawChecker({
			chrome: 71,
			chrome_android: 71,
			edge: 79,
			firefox: 65,
			firefox_android: 65,
			// ie: Not supported,
			nodejs: 12,
			opera: 58,
			opera_android: 50,
			safari: [12, 1],
			safari_ios: [12, 2],
			// cspell:word samsunginternet
			samsunginternet_android: [10, 0],
			webview_android: 71
		}),

		browser: browserProperty,
		electron: false,
		node: nodeProperty,
		nwjs: false,
		web: browserProperty,
		webworker: false,

		document: browserProperty,
		fetchWasm: browserProperty,
		global: nodeProperty,
		importScripts: false,
		importScriptsInWorker: true,
		nodeBuiltins: nodeProperty,
		require: nodeProperty
	};
};

module.exports = {
	resolve,
	parse
};
<commit_msg>always parse into an object<commit_after>/*
	MIT License http://www.opensource.org/licenses/mit-license.php
	Author Sergey Melyukov @smelukov
*/

"use strict";

const browserslist = require("browserslist");
const path = require("path");

/** @typedef {import("./target").ApiTargetProperties} ApiTargetProperties */
/** @typedef {import("./target").EcmaTargetProperties} EcmaTargetProperties */
/** @typedef {import("./target").PlatformTargetProperties} PlatformTargetProperties */

// [[C:]/path/to/config][:env]
const inputRx = /^(?:((?:[A-Z]:)?[/\\].*?))?(?::(.+?))?$/i;

/**
 * @typedef {Object} BrowserslistHandlerConfig
 * @property {string} [configPath]
 * @property {string} [env]
 * @property {string} [query]
 */

/**
 * @param {BrowserslistHandlerConfig} handlerConfig config
 * @returns {EcmaTargetProperties & PlatformTargetProperties & ApiTargetProperties} target properties
 */
const resolve = handlerConfig => {
	const { configPath, env, query } = handlerConfig;

	// if a query is specified, then use it, else
	// if a path to a config is specified then load it, else
	// find a nearest config
	const config = query
		? query
		: configPath
		? browserslist.loadConfig({
				config: configPath,
				env
		  })
		: browserslist.loadConfig({ path: process.cwd(), env });

	const browsers = browserslist(config);

	return resolveESFeatures(browsers);
};

/**
 * @param {string} input input string
 * @returns {BrowserslistHandlerConfig} config
 */
const parse = input => {
	if (!input) {
		return {};
	}

	if (path.isAbsolute(input)) {
		const [, configPath, env] = inputRx.exec(input) || [];
		return { configPath, env };
	}

	const config = browserslist.findConfig(process.cwd());

	if (config && Object.keys(config).includes(input)) {
		return { env: input };
	}

	return { query: input };
};

/**
 * @param {string[]} browsers supported browsers list
 * @returns {EcmaTargetProperties & PlatformTargetProperties & ApiTargetProperties} target properties
 */
const resolveESFeatures = browsers => {
	/**
	 * Checks only browser against the browserslist feature query
	 * @param {string} feature an ES feature to test
	 * @returns {boolean} true if supports
	 */
	const browserslistChecker = feature => {
		const supportsFeature = browserslist(`supports ${feature}`);
		return browsers.every(v => /^node /.test(v) || supportsFeature.includes(v));
	};
	/**
	 * Checks only node.js version against a version
	 * @param {number} major major version
	 * @param {number} minor minor version
	 * @returns {boolean} true if supports
	 */
	const nodeChecker = (major, minor = 0) => {
		return browsers.every(v => {
			const match = /^node (\d+)(?:\.\d+)?/.exec(v);
			if (!match) return true;
			const [, v1, v2] = match;
			return major === +v1 ? +v2 >= minor : +v1 > major;
		});
	};
	/**
	 * Checks all against a version number
	 * @param {Record<string, number | [number, number]} versions first supported version
	 * @returns {boolean} true if supports
	 */
	const rawChecker = versions => {
		return browsers.every(v => {
			const match = /^([^ ]+) (\d+)(?:\.\d+)?/.exec(v);
			if (!match) return false;
			const [, name, major, minor] = match;
			const version = versions[name];
			if (!version) return false;
			if (typeof version === "number") return +major >= version;
			return version[0] === +major ? +minor >= version[1] : +major > version[0];
		});
	};
	const anyNode = browsers.some(b => /^node /.test(b));
	const anyBrowser = browsers.some(b => /^(?!node)/.test(b));
	const browserProperty = !anyBrowser ? false : anyNode ? null : true;
	const nodeProperty = !anyNode ? false : anyBrowser ? null : true;
	const es6 = browserslistChecker("es6");
	const es6DynamicImport = browserslistChecker("es6-module-dynamic-import");
	const node6 = nodeChecker(6);
	return {
		const: es6 && node6,
		arrowFunction: es6 && node6,
		forOf: es6 && nodeChecker(5),
		destructuring: es6 && node6,
		bigIntLiteral: browserslistChecker("bigint") && nodeChecker(10, 4),
		module: browserslistChecker("es6-module") && nodeChecker(12, 17),
		dynamicImport: es6DynamicImport && nodeChecker(10, 17),
		dynamicImportInWorker: es6DynamicImport && nodeChecker(Infinity),
		// browserslist does not have info about globalThis
		// so this is based on mdn-browser-compat-data
		globalThis: rawChecker({
			chrome: 71,
			chrome_android: 71,
			edge: 79,
			firefox: 65,
			firefox_android: 65,
			// ie: Not supported,
			nodejs: 12,
			opera: 58,
			opera_android: 50,
			safari: [12, 1],
			safari_ios: [12, 2],
			// cspell:word samsunginternet
			samsunginternet_android: [10, 0],
			webview_android: 71
		}),

		browser: browserProperty,
		electron: false,
		node: nodeProperty,
		nwjs: false,
		web: browserProperty,
		webworker: false,

		document: browserProperty,
		fetchWasm: browserProperty,
		global: nodeProperty,
		importScripts: false,
		importScriptsInWorker: true,
		nodeBuiltins: nodeProperty,
		require: nodeProperty
	};
};

module.exports = {
	resolve,
	parse
};
<|endoftext|>


<commit_before>// Place your application-specific JavaScript functions and classes here
// This file is automatically included by javascript_include_tag :defaults

var FormWatch = Class.create();
FormWatch.prototype = {
   initialize : function(form, options) {
     this.submitted = false;
     this.form = $(form);
      // Let's serialize this.form and store it...
     this.formcontents = $(form).serialize();
      // Observe beforeunload event...
     Event.observe(this.form, 'submit', function() {this.submitted = true; }.bind(this));
     Event.observe(window, 'beforeunload', this.confirmExit.bind(this));
   },

  isDirty: function() {
    var newcontents = this.form.serialize();
    return this.formcontents != newcontents;
  },

  confirmExit : function(ev) {
    if (this.isDirty() && !(this.submitted)) {
      ev.returnValue = i18n.t('form_changed');
    }
  }
};

function mark_for_destroy(element) {
  $(element).next('.should_destroy').value = 1;
  $(element).up('.role_membership').hide();
}

function toggle_investigator_forms(id_to_show) {
  id_to_hide = $("active_form").value;
  id_to_hide = "form_investigate_" + id_to_hide;

  $("active_form").value = id_to_show;
  id_to_show = "form_investigate_" + id_to_show;

  $(id_to_hide).hide();
  $(id_to_show).show();
}

extractLocaleAsQueryString = function(path) {
  var qp = $H(path.toQueryParams());
  var l = qp.get('locale');
  if (l == undefined) {
    return "";
  } else {
    return "?" + "locale=" + l;
  }
};

joinAndPreserveLocale = function(base, ext) {
  var s = base.split("?");
  var p = s[0];
  if (s[1] == undefined) {
    var l = "";
  } else {
    var l = extractLocaleAsQueryString(base);
  }
  return p + ext + l;
};

scrollToTop = function() {
  $j(window).scrollTop(0);
  return null;
};

function sendConditionRequest(path, element, event_id, question_element_id, spinner_id) {
    if (typeof spinner_id == "undefined")
        spinner_id = 'investigator_answer_' +  question_element_id + '_spinner';
    if (path.indexOf('?') < 0) { path = path + "?"; }
    queryParams = path.toQueryParams();
    queryParams.question_element_id = question_element_id;
    queryParams.response = element.value;
    queryParams.event_id = event_id;
    if (window.location.toString().toQueryParams().locale) { queryParams.locale = window.location.toString().toQueryParams().locale; }
    new Ajax.Request(path.sub(/\?.*/, '') + '?' + $H(queryParams).toQueryString(), {
        asynchronous: true,
        evalScripts:  true,
        onCreate:     function() {
            $(spinner_id).show();
        },
        onComplete:   function() {
            $(spinner_id).hide();
        }
    });
}

function sendCoreConditionRequest(path, element, event_id, core_path, spinner_id) {
    if (typeof spinner_id == "undefined")
        spinner_id = core_path + '_spinner';
    if (path.indexOf('?') < 0) { path = path + "?"; }
    queryParams = path.toQueryParams();
    queryParams.core_path = core_path;
    queryParams.response = element.value;
    queryParams.event_id = event_id;
    if (window.location.toString().toQueryParams().locale) { queryParams.locale = window.location.toString().toQueryParams().locale; }
    new Ajax.Request(path.sub(/\?.*/, '') + '?' + $H(queryParams).toQueryString(), {
        asynchronous: true,
        evalScripts:  true,
        onCreate:     function() {
            $(spinner_id).show();
        },
        onComplete:   function() {
            $(spinner_id).hide();
        }
    });
}

function updateStatusRequest(path, value) {
    separator = (path.indexOf("?") > 0) ? "&" : "?";
    new Ajax.Request(path + separator + 'task[status]=' + value, {
        asynchronous:true,
        evalScripts:true,
        method:'put'
    });
}

function setUpSearchFields() {
    if (document.getElementById("name").value.length > 0) {
        document.getElementById("sw_first_name").disabled = true;
        document.getElementById("sw_last_name").disabled = true;
    }

    if (document.getElementById("sw_first_name").value.length > 0 || document.getElementById("sw_last_name").value.length > 0) {
        document.getElementById("name").disabled = true;
    }
}

function checkNameSearchFields(field) {
    if (field.id == "name") {
        if (field.value.length > 0) {
            document.getElementById("sw_first_name").disabled = true;
            document.getElementById("sw_last_name").disabled = true;
        }
        else {
            document.getElementById("sw_first_name").disabled = false;
            document.getElementById("sw_last_name").disabled = false;
        }
    }
    else if (field.id == "sw_first_name" || field.id == "sw_last_name") {
        first = document.getElementById("sw_first_name");
        last = document.getElementById("sw_last_name");

        if (first.value.length > 0 || last.value.length > 0) {
            document.getElementById("name").disabled = true;
        }
        else {
            document.getElementById("name").disabled = false;
        }
    }
}

function build_url_with_tab_index(url) {
    if (!(window.myTabs === undefined)) {
        if (url.indexOf('?') < 0) {
          url = url + "?";
        }
        queryParams = $H(url.toQueryParams());
        queryParams.set("tab_index", myTabs.get("activeIndex"));
        url = url.sub(/\?.*/, '') + "?" + queryParams.toQueryString();
    }
    return url;
}

function send_url_with_tab_index(url) {
    url = build_url_with_tab_index(url);
    location.href = url;
}

function add_tab_index_to_action(form) {
    form.action = build_url_with_tab_index(form.action);
}

function post_and_return(form_id) {
  form = document.getElementById(form_id);
  form.action = build_url_with_tab_index(form.action);
  form.action += form.action.match(/\?/) ? "" : "?";
  form.action = form.action + "&return=true";
  formWatcher.submitted = true;
  form.submit();
}

function post_and_exit(form) {
  form = $(form);
  url = build_url_with_tab_index(form.action);
  queryParams = url.toQueryParams();
  form.action = url;
  formWatcher.submitted = true;
  form.submit();
}

function toggle_strike_through(element_id) {
    Element.toggleClassName(element_id, 'struck-through');
}

function safe_disable(target) {
    element = document.getElementById(target);
    if (element) {
        element.disabled=true;
    }
}

function toggle_save_buttons(state) {
    btn1 = document.getElementById('save_and_exit_btn');
    btn2 = document.getElementById('save_and_continue_btn');

    var btns = new Array(btn1, btn2);
    $A(btns).each(function(btn) {
        if (btn) {
            if (state == 'on') {
                btn.disabled=false;
            } else {
                btn.disabled=true;
            }
        }
    });
}

function contact_parent_address(path) {
  new Ajax.Request(path, {
    asynchronous: true,
    evalScripts:  true,
    method:       'get',
    onComplete: function(transport, json) {
      $H(json).each(function(pair) {
        $('contact_event_address_attributes_' + pair.key).value = pair.value;
      });
    }
  });
}

function shortcuts_init(home_path, shortcuts_path) {
  this.root = home_path;
  new Ajax.Request(shortcuts_path, {
    method:      'get',
    asynchronous: true,
    evalScripts:  true,
    onComplete: function(transport, json) {
      $H(json).each(function(pair) {
        shortcut.add(pair.value, keymap[pair.key]);
      });
    }
  });
}

function shortcut_set(target) {
  var ele = $('user_shortcut_settings_' + target);
  var prev = ele.previous();
  ele.style.display = "none";
  prev.innerHTML = ele.value || i18n.t('undefined');
  prev.style.display = "inline";

  $$('input[type=text]').each(function(box) { if (box.style.display == "inline") return; });

  document.onkeydown = function (e) {
    e = e || window.event;
    if(e.preventDefault)
      e.preventDefault();

    submit_shortcuts(KeyCode.translate_event(e));
  };

  document.onkeypress = null;
  document.onkeyup = null;
}

function submit_shortcuts()
{
    var dirty, inline = false;

    if (!$('user_submit').disabled) {
      $('shortcut_form').submit();
    } else {
      $$('input[type=text]').each(function(box) {
        if (box.dirty)
          dirty = true;
        if (box.style.display == "inline")
          inline = true;
      });
      if (dirty && inline)
        alert(i18n.t('unresolved_conflicts'));
    }
}

function change_shortcut(ele) {
  shortcut.kill_shortcuts();
  ele.style.display = "inline";
  ele.previous().style.display = "none";
  ele.focus();

  document.onkeydown = function(e) {
    e = e || window.event;
    if(e.preventDefault)
      e.preventDefault();

    var key = KeyCode.translate_event(e);

    if (!(key.shift || key.alt || key.ctrl || key.meta)) {
      var ary = $$('input[type=text]');

      if (key.code == 13) {
        window.onkeydown = null;
        submit_shortcuts();
      }

      if (key.code == 38 || key.code == 40) {
        var i = ary.indexOf(ele) - (39 - key.code);
        if (i >= 0 && i < ary.length)
            change_shortcut(ary[i]);
      }

    } else if ((key.code < 16 || key.code > 18) && key.code != 224) {
      ele.value = KeyCode.hot_key(key);
      ele.dirty = "1";
      check_conflicts();
    }

    KeyCode.key_down(e);
    return false;
  };

  document.onkeypress = function(e) {
    //This event isn't sent in IE so we don't need to check preventDefault
    e.preventDefault();
    return false;
  };

  document.onkeyup = KeyCode.key_up;
}

function check_conflicts() {
  var button = $('user_submit');
  var fields = $$('input[type=text]');

  button.enable();

  fields.each(function(ele) {
    var conflict = false;

    fields.each(function(box) {
      if ((box.value != '') && (box != ele) && (box.value == ele.value)) {
        box.style.color = "#F00";
        box.previous().style.color = "#F00";
        ele.style.color = "#F00";
        ele.previous().style.color = "#F00";
        button.disable();
        conflict = true;
      }
    });

    if (!conflict) {
      ele.style.color = "";
      ele.previous().style.color = (ele.dirty ? "#4b4" : "");
    }
  });
}

function focus_first() {
    if (typeof(myTabs) != "undefined" && typeof(myTabs.get('activeTab')) != "undefined") {
        activeTab = myTabs.get('activeTab').get('contentEl');
    } else {
        activeTab = $('main-content');
    }
    try {
      YAHOO.util.Dom.getElementsBy(function(el) {
        return (el.tagName == 'SELECT' || el.tagName == 'INPUT' || el.tagName == 'A') && (el.type != "hidden");
      }, '', activeTab)[0].focus();
    } catch(error) {
      // no-op, the element cannot receive focus. Catch to allow further JS to not be interfered with on IE8
    }
}

function focus_init() {
  focus_first();
  if (typeof(myTabs) != "undefined")
    myTabs.addListener("activeIndexChange", focus_first);
}

function getLabOptions(select_list, url) {
  if (select_list.options[select_list.selectedIndex].value == "-1") {
    new Ajax.Updater(select_list,  url, {asynchronous:true, method: 'get'});
  }
}

function useGoogleApi(action) {
  if (Trisano.Geo.initialized) {
    action();
  } else {
    loadGoogleApi({ onSuccess: action });
  }
}

 function loadGoogleApi(options) {
   loadScript(Trisano.Geo.scriptSrc);
   options.onSuccess = options.onSuccess || Prototype.K;
   new PeriodicalExecuter(function(pe) {
     if (Trisano.Geo.initialized) {
       options.onSuccess();
       pe.stop();
     }
   }, 0.2);
 }

function googleMapsLoaded() {
  googleMapsInit();
}

Trisano = {
  baseUrl: null,

  url: function(path) {
    return Trisano.baseUrl + path.gsub(/^\/+/, '');
  },

  setBaseUrl: function(url) {
    Trisano.baseUrl = url || $$("script[@src*='javascripts/prototype.js']")[0]
      .getAttribute('src').gsub(/javascripts\/prototype.js.*$/, '');
  },

  flashError: function(msg) {
    if (!Object.isElement($('flash-message'))) {
      $$('#title_area .container')[0].insert( { top: new Element('div', { id: 'flash-message' }) } );
    }
    var element = $('flash-message');
    element.setAttribute('class', 'error-message');
    element.update(msg).show();
    return element;
  }
};

Trisano.setBaseUrl();

document.observe('trisano:dom:loaded', function() {
  if ($('flash-message')) {
    $('flash-message').fade({ delay: 3.0 });
  }

});

function moveMultiple(item, where) {
  liAncestor = $j(item).closest('li');
  ulAncestor = $j(liAncestor).closest('ul');

  switch(where) {
    case 'top':
      liAncestor.remove().prependTo(ulAncestor);
      break;
    case 'bottom':
      liAncestor.remove().appendTo(ulAncestor);
      break;
    case 'up':
      previousLi = liAncestor.prevAll('li:first');
      if (previousLi.size() != 0) { liAncestor.insertBefore(previousLi); }
      break;
    case 'down':
      nextLi = liAncestor.nextAll('li:first');
      if (nextLi.size() != 0) { liAncestor.insertAfter(nextLi); }
      break;
    default:
      // no-op
  }

  setMultiplesPositionAttributes(ulAncestor);
  return false;
}

function setMultiplesPositionAttributes(ul) {
  ul.find("li > input[name*='position']").each(function (index, element) { element.value = index+1; });
}
<commit_msg>Fixes an issue where loading a CMR in show mode displays the first tab with the page scrolled to the bottom, cutting off the tabs<commit_after>// Place your application-specific JavaScript functions and classes here
// This file is automatically included by javascript_include_tag :defaults

var FormWatch = Class.create();
FormWatch.prototype = {
   initialize : function(form, options) {
     this.submitted = false;
     this.form = $(form);
      // Let's serialize this.form and store it...
     this.formcontents = $(form).serialize();
      // Observe beforeunload event...
     Event.observe(this.form, 'submit', function() {this.submitted = true; }.bind(this));
     Event.observe(window, 'beforeunload', this.confirmExit.bind(this));
   },

  isDirty: function() {
    var newcontents = this.form.serialize();
    return this.formcontents != newcontents;
  },

  confirmExit : function(ev) {
    if (this.isDirty() && !(this.submitted)) {
      ev.returnValue = i18n.t('form_changed');
    }
  }
};

function mark_for_destroy(element) {
  $(element).next('.should_destroy').value = 1;
  $(element).up('.role_membership').hide();
}

function toggle_investigator_forms(id_to_show) {
  id_to_hide = $("active_form").value;
  id_to_hide = "form_investigate_" + id_to_hide;

  $("active_form").value = id_to_show;
  id_to_show = "form_investigate_" + id_to_show;

  $(id_to_hide).hide();
  $(id_to_show).show();
}

extractLocaleAsQueryString = function(path) {
  var qp = $H(path.toQueryParams());
  var l = qp.get('locale');
  if (l == undefined) {
    return "";
  } else {
    return "?" + "locale=" + l;
  }
};

joinAndPreserveLocale = function(base, ext) {
  var s = base.split("?");
  var p = s[0];
  if (s[1] == undefined) {
    var l = "";
  } else {
    var l = extractLocaleAsQueryString(base);
  }
  return p + ext + l;
};

scrollToTop = function() {
  $j(window).scrollTop(0);
  return null;
};

function sendConditionRequest(path, element, event_id, question_element_id, spinner_id) {
    if (typeof spinner_id == "undefined")
        spinner_id = 'investigator_answer_' +  question_element_id + '_spinner';
    if (path.indexOf('?') < 0) { path = path + "?"; }
    queryParams = path.toQueryParams();
    queryParams.question_element_id = question_element_id;
    queryParams.response = element.value;
    queryParams.event_id = event_id;
    if (window.location.toString().toQueryParams().locale) { queryParams.locale = window.location.toString().toQueryParams().locale; }
    new Ajax.Request(path.sub(/\?.*/, '') + '?' + $H(queryParams).toQueryString(), {
        asynchronous: true,
        evalScripts:  true,
        onCreate:     function() {
            $(spinner_id).show();
        },
        onComplete:   function() {
            $(spinner_id).hide();
        }
    });
}

function sendCoreConditionRequest(path, element, event_id, core_path, spinner_id) {
    if (typeof spinner_id == "undefined")
        spinner_id = core_path + '_spinner';
    if (path.indexOf('?') < 0) { path = path + "?"; }
    queryParams = path.toQueryParams();
    queryParams.core_path = core_path;
    queryParams.response = element.value;
    queryParams.event_id = event_id;
    if (window.location.toString().toQueryParams().locale) { queryParams.locale = window.location.toString().toQueryParams().locale; }
    new Ajax.Request(path.sub(/\?.*/, '') + '?' + $H(queryParams).toQueryString(), {
        asynchronous: true,
        evalScripts:  true,
        onCreate:     function() {
            $(spinner_id).show();
        },
        onComplete:   function() {
            $(spinner_id).hide();
        }
    });
}

function updateStatusRequest(path, value) {
    separator = (path.indexOf("?") > 0) ? "&" : "?";
    new Ajax.Request(path + separator + 'task[status]=' + value, {
        asynchronous:true,
        evalScripts:true,
        method:'put'
    });
}

function setUpSearchFields() {
    if (document.getElementById("name").value.length > 0) {
        document.getElementById("sw_first_name").disabled = true;
        document.getElementById("sw_last_name").disabled = true;
    }

    if (document.getElementById("sw_first_name").value.length > 0 || document.getElementById("sw_last_name").value.length > 0) {
        document.getElementById("name").disabled = true;
    }
}

function checkNameSearchFields(field) {
    if (field.id == "name") {
        if (field.value.length > 0) {
            document.getElementById("sw_first_name").disabled = true;
            document.getElementById("sw_last_name").disabled = true;
        }
        else {
            document.getElementById("sw_first_name").disabled = false;
            document.getElementById("sw_last_name").disabled = false;
        }
    }
    else if (field.id == "sw_first_name" || field.id == "sw_last_name") {
        first = document.getElementById("sw_first_name");
        last = document.getElementById("sw_last_name");

        if (first.value.length > 0 || last.value.length > 0) {
            document.getElementById("name").disabled = true;
        }
        else {
            document.getElementById("name").disabled = false;
        }
    }
}

function build_url_with_tab_index(url) {
    if (!(window.myTabs === undefined)) {
        if (url.indexOf('?') < 0) {
          url = url + "?";
        }
        queryParams = $H(url.toQueryParams());
        queryParams.set("tab_index", myTabs.get("activeIndex"));
        url = url.sub(/\?.*/, '') + "?" + queryParams.toQueryString();
    }
    return url;
}

function send_url_with_tab_index(url) {
    url = build_url_with_tab_index(url);
    location.href = url;
}

function add_tab_index_to_action(form) {
    form.action = build_url_with_tab_index(form.action);
}

function post_and_return(form_id) {
  form = document.getElementById(form_id);
  form.action = build_url_with_tab_index(form.action);
  form.action += form.action.match(/\?/) ? "" : "?";
  form.action = form.action + "&return=true";
  formWatcher.submitted = true;
  form.submit();
}

function post_and_exit(form) {
  form = $(form);
  url = build_url_with_tab_index(form.action);
  queryParams = url.toQueryParams();
  form.action = url;
  formWatcher.submitted = true;
  form.submit();
}

function toggle_strike_through(element_id) {
    Element.toggleClassName(element_id, 'struck-through');
}

function safe_disable(target) {
    element = document.getElementById(target);
    if (element) {
        element.disabled=true;
    }
}

function toggle_save_buttons(state) {
    btn1 = document.getElementById('save_and_exit_btn');
    btn2 = document.getElementById('save_and_continue_btn');

    var btns = new Array(btn1, btn2);
    $A(btns).each(function(btn) {
        if (btn) {
            if (state == 'on') {
                btn.disabled=false;
            } else {
                btn.disabled=true;
            }
        }
    });
}

function contact_parent_address(path) {
  new Ajax.Request(path, {
    asynchronous: true,
    evalScripts:  true,
    method:       'get',
    onComplete: function(transport, json) {
      $H(json).each(function(pair) {
        $('contact_event_address_attributes_' + pair.key).value = pair.value;
      });
    }
  });
}

function shortcuts_init(home_path, shortcuts_path) {
  this.root = home_path;
  new Ajax.Request(shortcuts_path, {
    method:      'get',
    asynchronous: true,
    evalScripts:  true,
    onComplete: function(transport, json) {
      $H(json).each(function(pair) {
        shortcut.add(pair.value, keymap[pair.key]);
      });
    }
  });
}

function shortcut_set(target) {
  var ele = $('user_shortcut_settings_' + target);
  var prev = ele.previous();
  ele.style.display = "none";
  prev.innerHTML = ele.value || i18n.t('undefined');
  prev.style.display = "inline";

  $$('input[type=text]').each(function(box) { if (box.style.display == "inline") return; });

  document.onkeydown = function (e) {
    e = e || window.event;
    if(e.preventDefault)
      e.preventDefault();

    submit_shortcuts(KeyCode.translate_event(e));
  };

  document.onkeypress = null;
  document.onkeyup = null;
}

function submit_shortcuts()
{
    var dirty, inline = false;

    if (!$('user_submit').disabled) {
      $('shortcut_form').submit();
    } else {
      $$('input[type=text]').each(function(box) {
        if (box.dirty)
          dirty = true;
        if (box.style.display == "inline")
          inline = true;
      });
      if (dirty && inline)
        alert(i18n.t('unresolved_conflicts'));
    }
}

function change_shortcut(ele) {
  shortcut.kill_shortcuts();
  ele.style.display = "inline";
  ele.previous().style.display = "none";
  ele.focus();

  document.onkeydown = function(e) {
    e = e || window.event;
    if(e.preventDefault)
      e.preventDefault();

    var key = KeyCode.translate_event(e);

    if (!(key.shift || key.alt || key.ctrl || key.meta)) {
      var ary = $$('input[type=text]');

      if (key.code == 13) {
        window.onkeydown = null;
        submit_shortcuts();
      }

      if (key.code == 38 || key.code == 40) {
        var i = ary.indexOf(ele) - (39 - key.code);
        if (i >= 0 && i < ary.length)
            change_shortcut(ary[i]);
      }

    } else if ((key.code < 16 || key.code > 18) && key.code != 224) {
      ele.value = KeyCode.hot_key(key);
      ele.dirty = "1";
      check_conflicts();
    }

    KeyCode.key_down(e);
    return false;
  };

  document.onkeypress = function(e) {
    //This event isn't sent in IE so we don't need to check preventDefault
    e.preventDefault();
    return false;
  };

  document.onkeyup = KeyCode.key_up;
}

function check_conflicts() {
  var button = $('user_submit');
  var fields = $$('input[type=text]');

  button.enable();

  fields.each(function(ele) {
    var conflict = false;

    fields.each(function(box) {
      if ((box.value != '') && (box != ele) && (box.value == ele.value)) {
        box.style.color = "#F00";
        box.previous().style.color = "#F00";
        ele.style.color = "#F00";
        ele.previous().style.color = "#F00";
        button.disable();
        conflict = true;
      }
    });

    if (!conflict) {
      ele.style.color = "";
      ele.previous().style.color = (ele.dirty ? "#4b4" : "");
    }
  });
}

function focus_first() {
    if (typeof(myTabs) != "undefined" && typeof(myTabs.get('activeTab')) != "undefined") {
        activeTab = myTabs.get('activeTab').get('contentEl');
    } else {
        activeTab = $('main-content');
    }
    try {
      YAHOO.util.Dom.getElementsBy(function(el) {
        return (el.tagName == 'SELECT' || el.tagName == 'INPUT' || el.tagName == 'A') && (el.type != "hidden");
      }, '', activeTab)[0].focus();
    } catch(error) {
      // no-op, the element cannot receive focus. Catch to allow further JS to not be interfered with on IE8
    }

    // focusing on an element that is far down the page may scroll the page in some browsers,
    // so ensure we are at the top
    jQuery('html, body').animate({ scrollTop: 0 }, 0);
}

function focus_init() {
  focus_first();
  if (typeof(myTabs) != "undefined")
    myTabs.addListener("activeIndexChange", focus_first);
}

function getLabOptions(select_list, url) {
  if (select_list.options[select_list.selectedIndex].value == "-1") {
    new Ajax.Updater(select_list,  url, {asynchronous:true, method: 'get'});
  }
}

function useGoogleApi(action) {
  if (Trisano.Geo.initialized) {
    action();
  } else {
    loadGoogleApi({ onSuccess: action });
  }
}

 function loadGoogleApi(options) {
   loadScript(Trisano.Geo.scriptSrc);
   options.onSuccess = options.onSuccess || Prototype.K;
   new PeriodicalExecuter(function(pe) {
     if (Trisano.Geo.initialized) {
       options.onSuccess();
       pe.stop();
     }
   }, 0.2);
 }

function googleMapsLoaded() {
  googleMapsInit();
}

Trisano = {
  baseUrl: null,

  url: function(path) {
    return Trisano.baseUrl + path.gsub(/^\/+/, '');
  },

  setBaseUrl: function(url) {
    Trisano.baseUrl = url || $$("script[@src*='javascripts/prototype.js']")[0]
      .getAttribute('src').gsub(/javascripts\/prototype.js.*$/, '');
  },

  flashError: function(msg) {
    if (!Object.isElement($('flash-message'))) {
      $$('#title_area .container')[0].insert( { top: new Element('div', { id: 'flash-message' }) } );
    }
    var element = $('flash-message');
    element.setAttribute('class', 'error-message');
    element.update(msg).show();
    return element;
  }
};

Trisano.setBaseUrl();

document.observe('trisano:dom:loaded', function() {
  if ($('flash-message')) {
    $('flash-message').fade({ delay: 3.0 });
  }

});

function moveMultiple(item, where) {
  liAncestor = $j(item).closest('li');
  ulAncestor = $j(liAncestor).closest('ul');

  switch(where) {
    case 'top':
      liAncestor.remove().prependTo(ulAncestor);
      break;
    case 'bottom':
      liAncestor.remove().appendTo(ulAncestor);
      break;
    case 'up':
      previousLi = liAncestor.prevAll('li:first');
      if (previousLi.size() != 0) { liAncestor.insertBefore(previousLi); }
      break;
    case 'down':
      nextLi = liAncestor.nextAll('li:first');
      if (nextLi.size() != 0) { liAncestor.insertAfter(nextLi); }
      break;
    default:
      // no-op
  }

  setMultiplesPositionAttributes(ulAncestor);
  return false;
}

function setMultiplesPositionAttributes(ul) {
  ul.find("li > input[name*='position']").each(function (index, element) { element.value = index+1; });
}
<|endoftext|>