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

/**
 * External dependencies
 */
import PropTypes from 'prop-types';
import { useCallback, useEffect, useRef, useState, useMemo } from 'react';

/**
 * WordPress dependencies
 */
import { __, sprintf } from '@wordpress/i18n';

/**
 * Internal dependencies
 */
import {
  DefaultLogoText,
  Error,
  GridItemButton,
  GridItemContainer,
  Logo,
  MenuContainer,
  LogoMenuButton,
  SettingForm,
  HelperText,
  FinePrintHelperText,
  UploadedContainer,
  SettingHeading,
} from '../components';
import { FileUpload } from '../../../../components';
import { EditPencil as EditPencilIcon } from '../../../../icons';
import { useGridViewKeys, useFocusOut } from '../../../../utils';
import { useConfig } from '../../../config';
import { PopoverMenuCard } from '../../../../components/popoverMenu';

export const TEXT = {
  SECTION_HEADING: __('Publisher Logo', 'web-stories'),
  CONTEXT: __(
    'Upload your logos here and they will become available to any stories you create.',
    'web-stories'
  ),
  INSTRUCTIONS: __(
    'Avoid vector files, such as svg or eps. Logos should be at least 96x96 pixels and a perfect square. The background should not be transparent.',
    'web-stories'
  ),
  SUBMIT: __('Upload logo', 'web-stories'),
  ARIA_LABEL: __('Click to upload a new logo', 'web-stories'),
  HELPER_UPLOAD: __(
    'Drag a jpg, png, or static gif in this box. Or click “Upload logo” below.',
    'web-stories'
  ),
};

const PUBLISHER_LOGO_CONTEXT_MENU_ACTIONS = {
  REMOVE_LOGO: 'remove_logo',
  SET_DEFAULT: 'set_default',
};

function PublisherLogoSettings({
  canUploadFiles,
  handleAddLogos,
  handleRemoveLogo,
  isLoading,
  publisherLogos,
  uploadError,
}) {
  const { isRTL } = useConfig();

  const containerRef = useRef();
  const gridRef = useRef();
  const itemRefs = useRef({});
  const popoverMenuContainerRef = useRef(null);

  const [activePublisherLogo, setActivePublisherLogoId] = useState(null);
  const [indexRemoved, setIndexRemoved] = useState(null);

  const [contextMenuId, setContextMenuId] = useState(null);

  const publisherLogosById = useMemo(() => publisherLogos.map(({ id }) => id), [
    publisherLogos,
  ]);

  const publisherLogoCount = useRef(publisherLogosById.length);

  const onRemoveLogoClick = useCallback(
    (publisherLogo, idx) => {
      handleRemoveLogo(publisherLogo);
      setIndexRemoved(idx);
      publisherLogoCount.current = publisherLogosById.length;
    },
    [handleRemoveLogo, publisherLogosById.length]
  );

  // // set active logo when first painting
  // useEffect(() => {
  //   if (publisherLogos.length > 0 && !activePublisherLogo) {
  //     setActivePublisherLogoId(publisherLogos?.[0].id);
  //   }
  // }, [activePublisherLogo, publisherLogos, setActivePublisherLogoId]);

  // Update publisher logo focus when logo is removed
  useEffect(() => {
    if (
      Boolean(indexRemoved?.toString()) &&
      publisherLogosById.length !== publisherLogoCount.current
    ) {
      if (publisherLogosById.length === 0) {
        // if the user has removed their last publisher logo, the logo grid will not render
        // the first element child of containerRef becomes the input upload
        // upload will always be present unless upload is not enabled.
        // currently only admin can get to settings, which means they have upload ability
        // checking for current and firstElementChild are safeguards
        return containerRef.current?.getElementsByTagName('input')[0].focus();
      }

      const moveItemFocusByIndex =
        indexRemoved > 0
          ? publisherLogosById[indexRemoved - 1]
          : publisherLogosById[0];

      setActivePublisherLogoId(moveItemFocusByIndex);
      itemRefs.current[moveItemFocusByIndex].firstChild.focus();
      return setIndexRemoved(null);
    }
    return undefined;
  }, [
    activePublisherLogo,
    indexRemoved,
    publisherLogosById,
    setActivePublisherLogoId,
  ]);

  useGridViewKeys({
    containerRef,
    gridRef,
    itemRefs,
    isRTL,
    currentItemId: activePublisherLogo,
    items: publisherLogos,
  });

  const onMoreButtonSelected = useCallback((openMenuLogoId) => {
    console.log('more button selected: ', openMenuLogoId);
    setContextMenuId(openMenuLogoId);
  }, []);

  const onMenuItemSelected = useCallback(
    (sender, logo, index) => {
      setContextMenuId(-1);

      switch (sender.value) {
        case PUBLISHER_LOGO_CONTEXT_MENU_ACTIONS.REMOVE_LOGO:
          onRemoveLogoClick(logo, index);
          break;

        case PUBLISHER_LOGO_CONTEXT_MENU_ACTIONS.SET_DEFAULT:
          break;

        default:
          break;
      }
    },
    [onRemoveLogoClick]
  );

  const handleFocusOut = useCallback(
    (publisherLogoId) => {
      if (contextMenuId === publisherLogoId) {
        onMoreButtonSelected(-1);
      }
    },
    [contextMenuId, onMoreButtonSelected]
  );

  useFocusOut(popoverMenuContainerRef, handleFocusOut, [contextMenuId]);

  return (
    <SettingForm>

        <SettingHeading>{TEXT.SECTION_HEADING}</SettingHeading>
        <HelperText>{TEXT.CONTEXT}</HelperText>

        {publisherLogos.length > 0 && (
          <UploadedContainer ref={gridRef} role="list" tabIndex={0}>
            {publisherLogos.map((publisherLogo, idx) => {
              if (!publisherLogo) {
                return null;
              }

              const isActive = activePublisherLogo === publisherLogo.id;
              const isPopoverMenuOpen = contextMenuId === publisherLogo.id;

              return (
                <GridItemContainer
                  key={`${publisherLogo.title}_${idx}`}
                  ref={(el) => {
                    itemRefs.current[publisherLogo.id] = el;
                  }}
                  role="listitem"
                >
                  <GridItemButton
                    data-testid={`publisher-logo-${idx}`}
                    isSelected={isActive}
                    tabIndex={isActive ? 0 : -1}
                    onClick={(e) => {
                      e.preventDefault();
                      setActivePublisherLogoId(publisherLogo.id);
                    }}
                    aria-label={
                      isActive
                        ? sprintf(
                            /* translators: %s: logo number.*/
                            __(
                              'Publisher Logo %s (currently selected)',
                              'web-stories'
                            ),
                            idx + 1
                          )
                        : sprintf(
                            /* translators: %s: logo number.*/
                            __('Publisher Logo %s', 'web-stories'),
                            idx + 1
                          )
                    }
                  >
                    <Logo src={publisherLogo.src} alt={publisherLogo.title} />
                  </GridItemButton>
                  {publisherLogo.isDefault && (
                    <DefaultLogoText>
                      {__('Default', 'web-stories')}
                    </DefaultLogoText>
                  )}
                  <MenuContainer ref={containerRef}>
                    <LogoMenuButton
                      tabIndex={isActive ? 0 : -1}
                      isActive={isActive}
                      menuOpen={isPopoverMenuOpen}
                      data-testid={`remove-publisher-logo-${idx}`}
                      aria-label={sprintf(
                        /* translators: %s: logo title */
                        __('Remove %s as a publisher logo', 'web-stories'),
                        publisherLogo.title
                      )}
                      onClick={(e) => {
                        e.preventDefault();
                        e.stopPropagation();
                        onMoreButtonSelected(
                          isPopoverMenuOpen ? -1 : publisherLogo.id
                        );
                      }}
                    >
                      <EditPencilIcon aria-hidden="true" />
                    </LogoMenuButton>
                    <PopoverMenuCard
                      isOpen={isPopoverMenuOpen}
                      onSelect={(menuItem) => {
                        onMenuItemSelected(menuItem, publisherLogo, idx);
                      }}
                      items={[
                        {
                          value:
                            PUBLISHER_LOGO_CONTEXT_MENU_ACTIONS.SET_DEFAULT,
                          label: __('Set as Default', 'web-stories'),
                        },
                        {
                          value:
                            PUBLISHER_LOGO_CONTEXT_MENU_ACTIONS.REMOVE_LOGO,
                          label: __('Delete', 'web-stories'),
                        },
                      ]}
                    />
                  </MenuContainer>
                </GridItemContainer>
              );
            })}
          </UploadedContainer>
        )}
        {uploadError && <Error>{uploadError}</Error>}
        {canUploadFiles && (
          <>
            <FileUpload
              onSubmit={handleAddLogos}
              id="settings_publisher_logos"
              isLoading={isLoading}
              label={TEXT.SUBMIT}
              isMultiple
              ariaLabel={TEXT.ARIA_LABEL}
              instructionalText={TEXT.HELPER_UPLOAD}
            />
            <FinePrintHelperText>{TEXT.INSTRUCTIONS}</FinePrintHelperText>
          </>
        )}

    </SettingForm>
  );
}

PublisherLogoSettings.propTypes = {
  canUploadFiles: PropTypes.bool,
  handleAddLogos: PropTypes.func,
  handleRemoveLogo: PropTypes.func,
  isLoading: PropTypes.bool,
  publisherLogos: PropTypes.arrayOf(
    PropTypes.shape({
      src: PropTypes.string,
      title: PropTypes.string,
      id: PropTypes.number,
    })
  ),
  uploadError: PropTypes.string,
};
export default PublisherLogoSettings;
<commit_msg>sorting out refs and when to focus on a logo in a grid instead of default now that menus are all hidden until you interact with them.<commit_after>/*
 * Copyright 2020 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * External dependencies
 */
import PropTypes from 'prop-types';
import { useCallback, useEffect, useRef, useState, useMemo } from 'react';

/**
 * WordPress dependencies
 */
import { __, sprintf } from '@wordpress/i18n';

/**
 * Internal dependencies
 */
import {
  DefaultLogoText,
  Error,
  GridItemButton,
  GridItemContainer,
  Logo,
  MenuContainer,
  LogoMenuButton,
  SettingForm,
  HelperText,
  FinePrintHelperText,
  UploadedContainer,
  SettingHeading,
} from '../components';
import { FileUpload } from '../../../../components';
import { EditPencil as EditPencilIcon } from '../../../../icons';
import { useGridViewKeys, useFocusOut } from '../../../../utils';
import { useConfig } from '../../../config';
import { PopoverMenuCard } from '../../../../components/popoverMenu';

export const TEXT = {
  SECTION_HEADING: __('Publisher Logo', 'web-stories'),
  CONTEXT: __(
    'Upload your logos here and they will become available to any stories you create.',
    'web-stories'
  ),
  INSTRUCTIONS: __(
    'Avoid vector files, such as svg or eps. Logos should be at least 96x96 pixels and a perfect square. The background should not be transparent.',
    'web-stories'
  ),
  SUBMIT: __('Upload logo', 'web-stories'),
  ARIA_LABEL: __('Click to upload a new logo', 'web-stories'),
  HELPER_UPLOAD: __(
    'Drag a jpg, png, or static gif in this box. Or click “Upload logo” below.',
    'web-stories'
  ),
};

const PUBLISHER_LOGO_CONTEXT_MENU_ACTIONS = {
  REMOVE_LOGO: 'remove_logo',
  SET_DEFAULT: 'set_default',
};

function PublisherLogoSettings({
  canUploadFiles,
  handleAddLogos,
  handleRemoveLogo,
  isLoading,
  publisherLogos,
  uploadError,
}) {
  const { isRTL } = useConfig();

  const containerRef = useRef();
  const gridRef = useRef();
  const itemRefs = useRef({});
  const popoverMenuContainerRef = useRef(null);

  const [activePublisherLogo, setActivePublisherLogoId] = useState(null);
  const [indexRemoved, setIndexRemoved] = useState(null);

  const [contextMenuId, setContextMenuId] = useState(null);

  const publisherLogosById = useMemo(() => publisherLogos.map(({ id }) => id), [
    publisherLogos,
  ]);

  const publisherLogoCount = useRef(publisherLogosById.length);

  const onRemoveLogoClick = useCallback(
    (publisherLogo, idx) => {
      handleRemoveLogo(publisherLogo);
      setIndexRemoved(idx);
      publisherLogoCount.current = publisherLogosById.length;
    },
    [handleRemoveLogo, publisherLogosById.length]
  );

  // Update publisher logo focus when logo is removed
  useEffect(() => {
    if (
      Boolean(indexRemoved?.toString()) &&
      publisherLogosById.length !== publisherLogoCount.current
    ) {
      if (publisherLogosById.length === 0) {
        // if the user has removed their last publisher logo, the logo grid will not render
        // the first element child of containerRef becomes the input upload
        // upload will always be present unless upload is not enabled.
        // currently only admin can get to settings, which means they have upload ability
        // checking for current and firstElementChild are safeguards
        return containerRef.current?.getElementsByTagName('input')[0].focus();
      }

      const moveItemFocusByIndex =
        indexRemoved > 0
          ? publisherLogosById[indexRemoved - 1]
          : publisherLogosById[0];

      setActivePublisherLogoId(moveItemFocusByIndex);
      itemRefs.current[moveItemFocusByIndex].firstChild.focus();
      return setIndexRemoved(null);
    }
    return undefined;
  }, [
    activePublisherLogo,
    indexRemoved,
    publisherLogosById,
    setActivePublisherLogoId,
  ]);

  useGridViewKeys({
    containerRef,
    gridRef,
    itemRefs,
    isRTL,
    currentItemId: activePublisherLogo,
    items: publisherLogos,
  });

  const onUploadedLogoContainerFocus = useCallback(
    (e) => {
      e.preventDefault();
      if (!activePublisherLogo) {
        setActivePublisherLogoId(publisherLogos?.[0].id);
        itemRefs.current?.[publisherLogos?.[0].id]?.children?.[0].focus();
      }
    },
    [activePublisherLogo, publisherLogos]
  );

  const onMoreButtonSelected = useCallback((openMenuLogoId) => {
    setContextMenuId(openMenuLogoId);
  }, []);

  const onMenuItemSelected = useCallback(
    (sender, logo, index) => {
      setContextMenuId(-1);

      switch (sender.value) {
        case PUBLISHER_LOGO_CONTEXT_MENU_ACTIONS.REMOVE_LOGO:
          onRemoveLogoClick(logo, index);
          break;

        case PUBLISHER_LOGO_CONTEXT_MENU_ACTIONS.SET_DEFAULT:
          break;

        default:
          break;
      }
    },
    [onRemoveLogoClick]
  );

  const handleFocusOut = useCallback(() => {
    if (contextMenuId === activePublisherLogo) {
      onMoreButtonSelected(-1);
    }
  }, [activePublisherLogo, contextMenuId, onMoreButtonSelected]);

  useFocusOut(popoverMenuContainerRef, handleFocusOut, [contextMenuId]);

  return (
    <SettingForm>

        <SettingHeading>{TEXT.SECTION_HEADING}</SettingHeading>
        <HelperText>{TEXT.CONTEXT}</HelperText>

        {publisherLogos.length > 0 && (
          <UploadedContainer
            ref={gridRef}
            role="list"
            tabIndex={0}
            onFocus={onUploadedLogoContainerFocus}
          >
            {publisherLogos.map((publisherLogo, idx) => {
              if (!publisherLogo) {
                return null;
              }

              const isActive = activePublisherLogo === publisherLogo.id;
              const isPopoverMenuOpen = contextMenuId === publisherLogo.id;

              return (
                <GridItemContainer
                  key={`${publisherLogo.title}_${idx}`}
                  ref={(el) => {
                    itemRefs.current[publisherLogo.id] = el;
                  }}
                  role="listitem"
                >
                  <GridItemButton
                    data-testid={`publisher-logo-${idx}`}
                    isSelected={isActive}
                    tabIndex={isActive ? 0 : -1}
                    onClick={(e) => {
                      e.preventDefault();
                      setActivePublisherLogoId(publisherLogo.id);
                    }}
                    aria-label={
                      isActive
                        ? sprintf(
                            /* translators: %s: logo number.*/
                            __(
                              'Publisher Logo %s (currently selected)',
                              'web-stories'
                            ),
                            idx + 1
                          )
                        : sprintf(
                            /* translators: %s: logo number.*/
                            __('Publisher Logo %s', 'web-stories'),
                            idx + 1
                          )
                    }
                  >
                    <Logo src={publisherLogo.src} alt={publisherLogo.title} />
                  </GridItemButton>
                  {publisherLogo.isDefault && (
                    <DefaultLogoText>
                      {__('Default', 'web-stories')}
                    </DefaultLogoText>
                  )}
                  <MenuContainer ref={popoverMenuContainerRef}>
                    <LogoMenuButton
                      tabIndex={isActive ? 0 : -1}
                      isActive={isActive}
                      menuOpen={isPopoverMenuOpen}
                      data-testid={`remove-publisher-logo-${idx}`}
                      aria-label={sprintf(
                        /* translators: %s: logo title */
                        __('Remove %s as a publisher logo', 'web-stories'),
                        publisherLogo.title
                      )}
                      onClick={(e) => {
                        e.preventDefault();
                        e.stopPropagation();
                        onMoreButtonSelected(
                          isPopoverMenuOpen ? -1 : publisherLogo.id
                        );
                      }}
                    >
                      <EditPencilIcon aria-hidden="true" />
                    </LogoMenuButton>
                    <PopoverMenuCard
                      isOpen={isPopoverMenuOpen}
                      onSelect={(menuItem) => {
                        onMenuItemSelected(menuItem, publisherLogo, idx);
                      }}
                      items={[
                        {
                          value:
                            PUBLISHER_LOGO_CONTEXT_MENU_ACTIONS.SET_DEFAULT,
                          label: __('Set as Default', 'web-stories'),
                        },
                        {
                          value:
                            PUBLISHER_LOGO_CONTEXT_MENU_ACTIONS.REMOVE_LOGO,
                          label: __('Delete', 'web-stories'),
                        },
                      ]}
                    />
                  </MenuContainer>
                </GridItemContainer>
              );
            })}
          </UploadedContainer>
        )}
        {uploadError && <Error>{uploadError}</Error>}
        {canUploadFiles && (
          <>
            <FileUpload
              onSubmit={handleAddLogos}
              id="settings_publisher_logos"
              isLoading={isLoading}
              label={TEXT.SUBMIT}
              isMultiple
              ariaLabel={TEXT.ARIA_LABEL}
              instructionalText={TEXT.HELPER_UPLOAD}
            />
            <FinePrintHelperText>{TEXT.INSTRUCTIONS}</FinePrintHelperText>
          </>
        )}

    </SettingForm>
  );
}

PublisherLogoSettings.propTypes = {
  canUploadFiles: PropTypes.bool,
  handleAddLogos: PropTypes.func,
  handleRemoveLogo: PropTypes.func,
  isLoading: PropTypes.bool,
  publisherLogos: PropTypes.arrayOf(
    PropTypes.shape({
      src: PropTypes.string,
      title: PropTypes.string,
      id: PropTypes.number,
    })
  ),
  uploadError: PropTypes.string,
};
export default PublisherLogoSettings;
<|endoftext|>


<commit_before>(function() {
  var numberOfButtons = 3
  var list = []

  for (var i = 1;i <= numberOfButtons;i++) {
    list.push('server.connect_to_host_'+i)
    api.settings.definitions.server.settings['connect_to_host_' + i] = {
      title: 'host ' + i,
      type: 'text',
      group: i.toString(),
      default: ''
    }
    list.push('server.connect_to_port_'+i)
    api.settings.definitions.server.settings['connect_to_port_' + i] = {
      title: 'port ' + i,
      type: 'text',
      group: i.toString(),
      default: ''
    }
  }

  api.settings.definitions.server.settings['connect_to_host_1'].default = 'localhost'
  api.settings.definitions.server.settings['connect_to_port_1'].default = '6543'

  // force model.settingsLists to update
  model.settingDefinitions(api.settings.definitions)

  var dummy = ko.observable('localhost')
  model.connectButtonsSettingList = list.map(function(s) {
    return model.settingsItemMap()[s]
  })

  var settingsHtml =
    '
' +
      '
' +
        '
' +
          '' +
            'title' +
          '' +
          '<input type="text" class="form-control" value="" data-bind="value: value" />' +
        '
' +
      '
' +
    '
'
  var $group = $(settingsHtml).appendTo('.option-list.server')
})()

<commit_msg>align host and port in settings<commit_after>(function() {
  var numberOfButtons = 3
  var groups = []

  for (var i = 1;i <= numberOfButtons;i++) {
    api.settings.definitions.server.settings['connect_to_host_' + i] = {
      title: 'host ' + i,
      type: 'text',
      group: i.toString(),
      default: ''
    }
    api.settings.definitions.server.settings['connect_to_port_' + i] = {
      title: 'port ' + i,
      type: 'text',
      group: i.toString(),
      default: ''
    }
  }

  api.settings.definitions.server.settings['connect_to_host_1'].default = 'localhost'
  api.settings.definitions.server.settings['connect_to_port_1'].default = '6543'

  // force model.settingsLists to update
  model.settingDefinitions(api.settings.definitions)

  var dummy = ko.observable('localhost')
  model.connectButtonsSettingGroups = []
  for (var i = 1;i <= numberOfButtons;i++) {
    model.connectButtonsSettingGroups[i-1] = {parts: [
      model.settingsItemMap()['server.connect_to_host_'+i],
      model.settingsItemMap()['server.connect_to_port_'+i]
    ]}
  }

  var settingsHtml =
    '
' +
      '
' +
        '
' +
          '' +
            'title' +
          '' +
          '<input type="text" class="form-control" value="" data-bind="value: value" />' +
        '
' +
      '
' +
    '
'
  var $group = $(settingsHtml).appendTo('.option-list.server')
})()

<|endoftext|>


<commit_before>'use strict';

require('../stylesheets/react-spinner.css');

import React, { Component, PropTypes } from 'react';
import { Button } from 'react-bootstrap';
import Spinner from './Spinner';
import TripIdeas from '../containers/TripIdeas';
import TripMap from '../containers/TripMap';
import { viewTripsPage } from '../actions/navigation';

class TripPage extends Component {
  componentWillMount() {
    const { tripId } = this.props.params;
    const { onGetTrip, trip } = this.props;

    // Fetch the trip from the server upon load
    if (!trip) {
      onGetTrip(tripId);
    }
  }

  render() {
    const { error, trip } = this.props;

    if (error) {
      return (

{error}

      );
    }

    // Loading UI
    if (!trip) {
      return (

Loading Trip

            <Spinner />

      );
    }

    const tripPlan = trip.plan.map(day => {
      return (

Day

      );
    });

    const titleSection = (

{trip.title}

Destination: {trip.destination && trip.destination.name}

Visibility: {trip.visibility}

    );

    return (

          {titleSection}
          <TripIdeas />
          <Button
            bsStyle="primary"
            onClick={viewTripsPage}>
            Home
          </Button>

          <TripMap />

    );
  }
}

TripPage.propTypes = {
  error: PropTypes.string,
  onGetTrip: PropTypes.func.isRequired,
  trip: PropTypes.object
};

const styles = {
  h1: {
    fontSize: 32
  },
  leftColumn: {
    float: 'left',
    padding: '0 20 0',
    width: 400
  },
  loadingText: {
    color: '#333333',
    fontFamily: 'Arial',
    fontSize: 24,
    textAlign: 'center',
    margin: 20
  },
  rightColumn: {
    marginLeft: 400,
    padding: '0 20 0'
  },
  titleSection: {
    marginBottom: 30
  }
};

export default TripPage;
<commit_msg>Set overflow CSS setting for ideas section<commit_after>'use strict';

require('../stylesheets/react-spinner.css');

import React, { Component, PropTypes } from 'react';
import { Button } from 'react-bootstrap';
import Spinner from './Spinner';
import TripIdeas from '../containers/TripIdeas';
import TripMap from '../containers/TripMap';
import { viewTripsPage } from '../actions/navigation';

class TripPage extends Component {
  componentWillMount() {
    const { tripId } = this.props.params;
    const { onGetTrip, trip } = this.props;

    // Fetch the trip from the server upon load
    if (!trip) {
      onGetTrip(tripId);
    }
  }

  render() {
    const { error, trip } = this.props;

    if (error) {
      return (

{error}

      );
    }

    // Loading UI
    if (!trip) {
      return (

Loading Trip

            <Spinner />

      );
    }

    const tripPlan = trip.plan.map(day => {
      return (

Day

      );
    });

    const titleSection = (

{trip.title}

Destination: {trip.destination && trip.destination.name}

Visibility: {trip.visibility}

    );

    return (

          {titleSection}
          <TripIdeas />
          <Button
            bsStyle="primary"
            onClick={viewTripsPage}>
            Home
          </Button>

          <TripMap />

    );
  }
}

TripPage.propTypes = {
  error: PropTypes.string,
  onGetTrip: PropTypes.func.isRequired,
  trip: PropTypes.object
};

const styles = {
  h1: {
    fontSize: 32
  },
  leftColumn: {
    float: 'left',
    height: '100%',
    overflow: 'scroll',
    padding: '0 30 0',
    width: 400
  },
  loadingText: {
    color: '#333333',
    fontFamily: 'Arial',
    fontSize: 24,
    textAlign: 'center',
    margin: 20
  },
  rightColumn: {
    marginLeft: 400
  },
  titleSection: {
    marginBottom: 30
  }
};

export default TripPage;
<|endoftext|>


<commit_before>var CodeMirror = require('codemirror')
  , bindCodemirror = require('gulf-codemirror')
  , vdom = require('virtual-dom')
  , h = vdom.h

const SET_MODE = 'EDITORTEXTCODEMIRROR_SET_MODE'
const TOGGLE_LINENUMBERS = 'EDITORTEXTCODEMIRROR_TOGGLE_LINENUMBERS'
const SET_LINENUMBERS = 'EDITORTEXTCODEMIRROR_SET_LINENUMBERS'

module.exports = setup
module.exports.consumes = ['ui', 'editor', 'settings']
module.exports.provides = []
function setup(plugin, imports, register) {
  var ui = imports.ui
    , editor = imports.editor
    , settings = imports.settings

  ui.reduxReducerMap.editorTextCodemirror = reducer
  function reducer(state, action) {
    if(!state) return {mode: null, lineNumbers: false}
    if(SET_MODE === action.type) {
      return {...state, mode: action.payload}
    }
    if(TOGGLE_LINENUMBERS === action.type) {
      return {...state, lineNumbers: !state.lineNumbers}
    }
    if(SET_LINENUMBERS === action.type) {
      return {...state, lineNumbers: action.payload}
    }
    return state
  }

  editor.registerEditor('CodeMirror', 'text', 'An extensible and performant code editor'
  , function(editorEl) {
    // Overtake settings
    setImmediate(function() {
      ui.store.dispatch(action_setLinenumbers(
        settings.getForUserDocument('editorTextCodemirror:lineNumbers')
      || settings.getForDocument('editorTextCodemirror:lineNumbers')
      ))

      ui.store.dispatch(action_setMode(
        settings.getForUserDocument('editorTextCodemirror:mode')
      || settings.getForDocument('editorTextCodemirror:mode')
      ))
    })

    var cmEl
    var cm = CodeMirror(function(el) {
      editorEl.appendChild(el)
      el.style['height'] = '100%'
      cmEl = el
    }, ui.store.getState().editorTextCodemirror)

    editorEl.style['height'] = '100%'

    var tree = render(ui.store, ui.config)
      , root = vdom.create(tree)
    cmEl.appendChild(root)

    ui.store.subscribe(_ => {
      var state = ui.store.getState()

      // update controls
      var newtree = render(ui.store, ui.config)
      vdom.patch(root, vdom.diff(tree, newtree))

      if(cm.getOption('mode') != state.editorTextCodemirror.mode) {
        cm.setOption('mode', state.editorTextCodemirror.mode)
      }
      if(cm.getOption('lineNumbers') != state.editorTextCodemirror.lineNumbers) {
        cm.setOption('lineNumbers', state.editorTextCodemirror.lineNumbers)
      }
    })

    return Promise.resolve(bindCodemirror(cm))
  })

  // Document Settings

  settings.onRenderDocumentSettings((children) => {
    if(ui.store.getState().editor.document.type !== 'text') return

    children.push(
      renderlineNumberSetting(settings.getForDocument('editorTextCodemirror:lineNumbers')
      , evt => {
          ui.store.dispatch(settings.action_setForDocument({
            'editorTextCodemirror:lineNumbers': evt.currentTarget.checked
          }))
        }
      )
    )
    children.push(
      renderModeSetting(settings.getForDocument('editorTextCodemirror:mode')
      , evt => {
          ui.store.dispatch(settings.action_setForDocument({
            'editorTextCodemirror:mode': evt.currentTarget.value
          }))
        }
      )
    )
  })

  settings.onRenderUserDocumentSettings((children) => {
    if(ui.store.getState().editor.document.type !== 'text') return

    children.push(
      renderlineNumberSetting(settings.getForUserDocument('editorTextCodemirror:lineNumbers')
      , evt => {
          ui.store.dispatch(settings.action_setForUserDocument({
            'editorTextCodemirror:lineNumbers': evt.currentTarget.checked
          }))
        }
      )
    )
    children.push(
      renderModeSetting(settings.getForUserDocument('editorTextCodemirror:mode')
      , evt => {
          ui.store.dispatch(settings.action_setForUserDocument({
            'editorTextCodemirror:mode': evt.currentTarget.value
          }))
        }
      )
    )
  })

  function renderlineNumberSetting(checked, cb) {
    return h('li.list-group-item', [
      h('input', {
        type: 'checkbox'
      , 'ev-change': cb
      , checked
      })
    , 'Show line numbers'
    ])
  }

  function renderModeSetting(currentMode, cb) {
    return h('li.list-group-item', [
      'Syntax highlighting '
    , h('select'
      , { 'ev-change': cb, value: currentMode }
      , [h('option', {value: ''}, 'Select a language')]
        .concat(ui.config.editorTextCodemirror.modes.map(mode => {
          return h('option'
          , {value: mode, attributes: currentMode == mode? {selected: true} : {}}
          , mode)
        }))
      )
    ])
  }

  register()
}

function action_setLinenumbers(value) {
  return {type: SET_LINENUMBERS, payload: value}
}

function action_toggleLinenumbers() {
  return {type: TOGGLE_LINENUMBERS}
}

function action_setMode(mode) {
  return {type: SET_MODE, payload: mode}
}

function render(store, config) {
  var state = store.getState().editorTextCodemirror
  return h('div.EditorTextCodemirror__controls', [
    h('a.EditorTextCodemirror__controls__toggleLinenumbers'
    , { href: 'javascript:void(0)'
      , 'ev-click': evt => store.dispatch(action_toggleLinenumbers())
      }
    , state.lineNumbers? 'Line numbers on' : 'line numbers off')
  , ' '
  , h('select.EditorTextCodemirror__controls__selectMode'
    , { 'ev-change': evt => store.dispatch(action_setMode(evt.currentTarget.value))
      }
    , [h('option', {value: ''}, 'Select a language')]
      .concat(config.editorTextCodemirror.modes.map(mode => {
        return h('option'
        , {value: mode, attributes: state.mode == mode? {selected: true} : {}}
        , mode)
      }))
    )
  ])
}
<commit_msg>Refactor settings hook + return a whole list-group<commit_after>var CodeMirror = require('codemirror')
  , bindCodemirror = require('gulf-codemirror')
  , vdom = require('virtual-dom')
  , h = vdom.h

const SET_MODE = 'EDITORTEXTCODEMIRROR_SET_MODE'
const TOGGLE_LINENUMBERS = 'EDITORTEXTCODEMIRROR_TOGGLE_LINENUMBERS'
const SET_LINENUMBERS = 'EDITORTEXTCODEMIRROR_SET_LINENUMBERS'

module.exports = setup
module.exports.consumes = ['ui', 'editor', 'settings']
module.exports.provides = []
function setup(plugin, imports, register) {
  var ui = imports.ui
    , editor = imports.editor
    , settings = imports.settings

  ui.reduxReducerMap.editorTextCodemirror = reducer
  function reducer(state, action) {
    if(!state) return {mode: null, lineNumbers: false}
    if(SET_MODE === action.type) {
      return {...state, mode: action.payload}
    }
    if(TOGGLE_LINENUMBERS === action.type) {
      return {...state, lineNumbers: !state.lineNumbers}
    }
    if(SET_LINENUMBERS === action.type) {
      return {...state, lineNumbers: action.payload}
    }
    return state
  }

  editor.registerEditor('CodeMirror', 'text', 'An extensible and performant code editor'
  , function(editorEl) {
    // Overtake settings
    setImmediate(function() {
      ui.store.dispatch(action_setLinenumbers(
        settings.getForUserDocument('editorTextCodemirror:lineNumbers')
      || settings.getForDocument('editorTextCodemirror:lineNumbers')
      ))

      ui.store.dispatch(action_setMode(
        settings.getForUserDocument('editorTextCodemirror:mode')
      || settings.getForDocument('editorTextCodemirror:mode')
      ))
    })

    var cmEl
    var cm = CodeMirror(function(el) {
      editorEl.appendChild(el)
      el.style['height'] = '100%'
      cmEl = el
    }, ui.store.getState().editorTextCodemirror)

    editorEl.style['height'] = '100%'

    var tree = render(ui.store, ui.config)
      , root = vdom.create(tree)
    cmEl.appendChild(root)

    ui.store.subscribe(_ => {
      var state = ui.store.getState()

      // update controls
      var newtree = render(ui.store, ui.config)
      vdom.patch(root, vdom.diff(tree, newtree))

      if(cm.getOption('mode') != state.editorTextCodemirror.mode) {
        cm.setOption('mode', state.editorTextCodemirror.mode)
      }
      if(cm.getOption('lineNumbers') != state.editorTextCodemirror.lineNumbers) {
        cm.setOption('lineNumbers', state.editorTextCodemirror.lineNumbers)
      }
    })

    return Promise.resolve(bindCodemirror(cm))
  })

  // Document Settings

  settings.onRenderDocumentSettings((children) => {
    if(ui.store.getState().editor.document.type !== 'text') return
    children.push(renderSettings(ui.store
    , settings.getForDocument.bind(settings), settings.action_setForDocument.bind(settings)))
  })

  settings.onRenderUserDocumentSettings((children) => {
    if(ui.store.getState().editor.document.type !== 'text') return
    children.push(renderSettings(ui.store
    , settings.getForUserDocument.bind(settings), settings.action_setForUserDocument.bind(settings)))
  })

  function renderSettings(store, getSetting, actionSave) {
    return h('div',[
      h('h4', 'Codemirror')
    , h('ul.list-group', [
        renderlineNumberSetting(getSetting('editorTextCodemirror:lineNumbers')
        , evt => {
            ui.store.dispatch(actionSave({
              'editorTextCodemirror:lineNumbers': evt.currentTarget.checked
            }))
          }
        )
      , renderModeSetting(getSetting('editorTextCodemirror:mode')
        , evt => {
            ui.store.dispatch(actionSave({
              'editorTextCodemirror:mode': evt.currentTarget.value
            }))
          }
        )
      ])
    ])
  }

  function renderlineNumberSetting(checked, cb) {
    return h('li.list-group-item', [
      h('label', [
        h('input', {
          type: 'checkbox'
        , 'ev-change': cb
        , checked
        })
      , ' Show line numbers'
      ])
    ])
  }

  function renderModeSetting(currentMode, cb) {
    return h('li.list-group-item', [
      h('label', [
        'Syntax highlighting: '
      , h('select'
        , { 'ev-change': cb, value: currentMode }
        , [h('option', {value: ''}, 'Select a language')]
          .concat(ui.config.editorTextCodemirror.modes.map(mode => {
            return h('option'
            , {value: mode, attributes: currentMode == mode? {selected: true} : {}}
            , mode)
          }))
        )
      ])
    ])
  }

  register()
}

function action_setLinenumbers(value) {
  return {type: SET_LINENUMBERS, payload: value}
}

function action_toggleLinenumbers() {
  return {type: TOGGLE_LINENUMBERS}
}

function action_setMode(mode) {
  return {type: SET_MODE, payload: mode}
}

function render(store, config) {
  var state = store.getState().editorTextCodemirror
  return h('div.EditorTextCodemirror__controls', [
    h('a.EditorTextCodemirror__controls__toggleLinenumbers'
    , { href: 'javascript:void(0)'
      , 'ev-click': evt => store.dispatch(action_toggleLinenumbers())
      }
    , state.lineNumbers? 'Line numbers on' : 'line numbers off')
  , ' '
  , h('select.EditorTextCodemirror__controls__selectMode'
    , { 'ev-change': evt => store.dispatch(action_setMode(evt.currentTarget.value))
      }
    , [h('option', {value: ''}, 'Select a language')]
      .concat(config.editorTextCodemirror.modes.map(mode => {
        return h('option'
        , {value: mode, attributes: state.mode == mode? {selected: true} : {}}
        , mode)
      }))
    )
  ])
}
<|endoftext|>


<commit_before>app.Map = function(params) {

    var locationParams = params.location;
	var map = new L.Map(params.elmId, {
			center: [ locationParams.center.lat, locationParams.center.lng ],
			zoom: locationParams.zoom
		})
		.addLayer(new L.TileLayer('http://{s}.tile.cloudmade.com/' + params.key + '/998/256/{z}/{x}/{y}.png'))
		.on("viewreset", onViewReset);

	var
		svg,
		g,

		selectedFeature,
		transform,
		path,
		clustersSelection,
		venuesSelection,
		layerPoints,

		colors = params.colors,
 		minCircle = 3,
    maxCircle = 30,

    tooltip;

	drawBorder(locationParams.bounds, map);
	svg = d3.select(map.getPanes().overlayPane).append("svg");
	g = svg.append("g").attr("class", "leaflet-zoom-hide");

	tooltip = Tooltip({
		elmId: "tooltip",
		width: 240
	});

	function drawBorder(bounds, map) {
		var pointSW = [bounds.sud, bounds.west];
		var pointNE = [bounds.north, bounds.est];
		var border = [pointSW, pointNE];
		var styles = { stroke: true, color: '#000', weight: 5, fillOpacity: 0 };
		L.rectangle(border, {className: 'border'}).addTo(map);
	}

	function initPath() {

		transform = d3.geo.transform({ point: function projectPoint(x, y) {
			var point = map.latLngToLayerPoint(new L.LatLng(y, x));
			this.stream.point(point.x, point.y);
		}});

		path = d3.geo.path().projection(transform);
	}

	function getBounds(feature) {

		var allBounds = feature.features.map(function(subfeature) {
			return path.bounds(subfeature);
		});

		var topLeftX = allBounds.map(function(bounds) { return bounds[0][0]; });
		var topLeftY = allBounds.map(function(bounds) { return bounds[0][1]; });
		var bottomRightX = allBounds.map(function(bounds) { return bounds[1][0]; });
		var bottomRightY = allBounds.map(function(bounds) { return bounds[1][1]; });

		var topLeftTotal = [Math.min.apply(null, topLeftX), Math.min.apply(null, topLeftY)];
		var bottomRightTotal = [Math.max.apply(null, bottomRightX), Math.max.apply(null, bottomRightY)];
		var boundsTotal = [topLeftTotal, bottomRightTotal];

		return boundsTotal;
	}

	function getAllCoords(feature) {
		var groupedCoords = feature.features.map(function(subfeature) {
			return subfeature.geometry.geometries[1].coordinates[0];
		});
		return _.flatten(groupedCoords, true);
	}

	function updateClustersSelection(selectedFeature, params) {
		// clustersSelection = svg polygon elements
		// representing clusters (hulls)

        var infoTemplate = _.template(d3.select('#cluster-tooltip').html());

		clustersSelection = g.selectAll("path.cluster")
			.data(selectedFeature.features, function(feature) {
				return feature.properties.clusterId;
			});

		clustersSelection.enter().append("path")
			.attr('data-href', function(feature) {
					var props = feature.properties,
							hash = [
								'#!' + params.location,
								// 'category', props.name,
								'clusters', props.clusterId
							].join('/');
				return hash;
			})
            .classed('cluster', true)
			.style('fill', function(feature) {
				return colors[feature.properties.name];
			})
			.style('stroke', function(feature) {
				return colors[feature.properties.name];
			})
            .on("mouseover", function(d) {
                var data = {
                    name: d.properties.name,
                    idCluster: d.properties.idCluster,
                    venuesLen: d.properties.venues.length
                }
                tooltip.showTooltip(infoTemplate({ data: data }), d3.event);
            })
            .on("mouseout", tooltip.hideTooltip);

		clustersSelection.exit().remove();
	}

	function updateVenuesSelection(selectedFeature) {

        var infoTemplate = _.template(d3.select('#venue-tooltip').html());

		var venues = [];
		if (selectedFeature.features.length === 1) {
			var venues = getVenues(selectedFeature);
		}

		// function groupVenues(venues) {
		// 	 return _(venues).groupBy(function(v) {
		// 		 return v.category;
		// 	 });
		// }

  //       var catNames = _(groupVenues(venues)).keys();

		// var fillColor = d3.scale.ordinal()
		// 	 .domain(catNames)
		// 		.range(catNames.map(function(catName) { return colors[catName]; }));

		var maxBeenHere = _(venues).pluck('beenHere')[0];

		var radiusScale = d3.scale.pow()
			.exponent(0.5)
			.domain([0, maxBeenHere])
			.range([minCircle, maxCircle]);

		// venuesSelection svg circle elements
		// representing venues
		venuesSelection = g.selectAll('circle')
			.data(venues, function(venue) {
				return venue.id;
			});

		venuesSelection.enter().append('circle')
			.attr('r', function(d) {
				return radiusScale(d.beenHere) || 1;
			})
            // .style('fill', function(d) {
            //     return fillColor(d.category);
            // })
			.on("mouseover", function(d) {
				d.date = moment(d.timestamp).fromNow();
				tooltip.showTooltip(infoTemplate({ data: d }), d3.event);
			})
			.on("mouseout", tooltip.hideTooltip)
			.on('click', function(d) {
				window.open(
					'https://foursquare.com/v/' + d.id,
					'_blank'
				);
			});

		venuesSelection.exit().remove();
	}

	function update(feature, params) {

		selectedFeature = feature;

		updateClustersSelection(selectedFeature, params);
		updateVenuesSelection(selectedFeature);

		onViewReset();

		// debugger;
		map.fitBounds(getAllCoords(selectedFeature).map(app.utils.inv));
	}

	function onViewReset() {

		initPath();

		var bounds = getBounds(selectedFeature),
			// var bounds = path.bounds(selectedFeature),
			topLeft = bounds[0],
			bottomRight = bounds[1],
            // padding = 2 * maxCircle;
            padding = Math.pow(maxCircle, map.getZoom() / 10);

		svg .attr("width", bottomRight[0] - topLeft[0] + (2 * padding))
				.attr("height", bottomRight[1] - topLeft[1] + (2 * padding))
				.style("left", (topLeft[0] - padding) + "px")
				.style("top", (topLeft[1] - padding) + "px");

		g	 .attr("transform", "translate(" + (-topLeft[0] + padding) + "," + (-topLeft[1] + padding) + ")");

		clustersSelection.attr("d", function(feature) {
				var geo = feature.geometry.geometries[1];
				var coords = geo.coordinates[0];
				coords.push(coords[0]);
				geo.coordinates = [coords];

				return path(geo);
		}.bind(this));

		venuesSelection.each(function(venue) {
			var point = map.latLngToLayerPoint(new L.LatLng(venue.venueLatitude, venue.venueLongitude));
			d3.select(this)
				.attr('cx', point.x)
				.attr('cy', point.y);
		});

		// removePoints();
		// removeVenues();

		// show only if one cluster
		if (selectedFeature.features.length === 1) {

			// addPoints();
			// addVenues();
		}
	}

	function addPoints(limit, everyNth) {

		var limit = limit || 1000;
		var everyNth = everyNth || 2;

		var markers = _(selectedFeature.features.map(function(feature) {
			var coordinates = feature.geometry.geometries[0].coordinates;
			return coordinates.map(app.utils.inv)
				.filter(function(coords, i) { return i % everyNth === 0; })
				.slice(0, limit)
				.map(function(coords) {
					return L.marker(coords, { icon: L.divIcon({ className: 'grid-dot' }) });
				});
		})).flatten();

		layerPoints = L.layerGroup(markers).addTo(map);
	}

	function removePoints(argument) {
		if (layerPoints) { map.removeLayer(layerPoints); }
	}

	function getVenues(selectedFeature, limit) {

		limit = limit || 300;

		var coords, marker,
				markers = [];

		var feature = selectedFeature.features[0];
		var categoriesToShow = categoriesFromFeature(feature.properties.name);
		var venues = selectedFeature.features[0].properties.venues;

		var venuesInCategory = venues.filter(function(venue) {
			return (categoriesToShow.indexOf(venue.category) >= 0);
		});

		// if there are no venues that share the same categories as the cluster
		// show all the venues
		if (venuesInCategory.length > 0) {
			venues = venuesInCategory;
		}

		return venues.sort(sortVenue)
			.slice(0, limit);

		function sortVenue(v1, v2) {
			// beenHere is sometimes undefined!
			// this caused sort to fail, that led to huge circle bug
			return (v2.beenHere || 0) - (v1.beenHere || 0);
		}

		function categoriesFromFeature(categoryName) {

			var regex = /c\((.*?)\)/;
			var matched = regex.exec(categoryName);

			if (matched) {
				return matched[1].split(',');
			}
			return null;
		}
	}

	// function createMarkers(coords, icon) {
	//	 var options = {};
	//	 if (icon) { options.icon = icon; }
	//	 return coords.map(function(marker) {
	//		 return L.marker(marker, options);
	//			// .bindPopup(renderPopup()); // .addTo(map);
	//	 })
	//	 .filter(function(d, i) {
	//		 return i % 10 === 0;
	//	 });
	// }

	return {
        locationParams: locationParams,
		update: update,
		get: function() {
			return map;
		}
	}
};<commit_msg>[ui] reformatted map.js<commit_after>app.Map = function(params) {

  var
    locationParams = params.location,
    map = new L.Map(params.elmId, {
        center: [ locationParams.center.lat, locationParams.center.lng ],
        zoom: locationParams.zoom
      })
      .addLayer(new L.TileLayer('http://{s}.tile.cloudmade.com/' + params.key + '/998/256/{z}/{x}/{y}.png'))
      .on("viewreset", onViewReset),

    svg,
    g,

    selectedFeature,
    transform,
    path,
    clustersSelection,
    venuesSelection,
    layerPoints,

    colors = params.colors,
    minCircle = 3,
    maxCircle = 30,

    tooltip;

  drawBorder(locationParams.bounds, map);
  svg = d3.select(map.getPanes().overlayPane).append("svg");
  g = svg.append("g").attr("class", "leaflet-zoom-hide");

  tooltip = Tooltip({
    elmId: "tooltip",
    width: 240
  });

  function drawBorder(bounds, map) {
    var pointSW = [bounds.sud, bounds.west];
    var pointNE = [bounds.north, bounds.est];
    var border = [pointSW, pointNE];
    var styles = { stroke: true, color: '#000', weight: 5, fillOpacity: 0 };

    L.rectangle(border, {className: 'border'}).addTo(map);
  }

  function initPath() {
    transform = d3.geo.transform({ point: function projectPoint(x, y) {
      var point = map.latLngToLayerPoint(new L.LatLng(y, x));
      this.stream.point(point.x, point.y);
    }});

    path = d3.geo.path().projection(transform);
  }

  function getBounds(feature) {
    var allBounds = feature.features.map(function(subfeature) {
      return path.bounds(subfeature);
    });

    var topLeftX = allBounds.map(function(bounds) { return bounds[0][0]; });
    var topLeftY = allBounds.map(function(bounds) { return bounds[0][1]; });
    var bottomRightX = allBounds.map(function(bounds) { return bounds[1][0]; });
    var bottomRightY = allBounds.map(function(bounds) { return bounds[1][1]; });

    var topLeftTotal = [Math.min.apply(null, topLeftX), Math.min.apply(null, topLeftY)];
    var bottomRightTotal = [Math.max.apply(null, bottomRightX), Math.max.apply(null, bottomRightY)];
    var boundsTotal = [topLeftTotal, bottomRightTotal];

    return boundsTotal;
  }

  function getAllCoords(feature) {
    var groupedCoords = feature.features.map(function(subfeature) {
      return subfeature.geometry.geometries[1].coordinates[0];
    });
    return _.flatten(groupedCoords, true);
  }

  function updateClustersSelection(selectedFeature, params) {
    // clustersSelection = svg polygon elements
    // representing clusters (hulls)

    var infoTemplate = _.template(d3.select('#cluster-tooltip').html());

    clustersSelection = g.selectAll("path.cluster")
      .data(selectedFeature.features, function(feature) {
        return feature.properties.clusterId;
      });

    clustersSelection.enter().append("path")
      .attr('data-href', function(feature) {
        var props = feature.properties,
          hash = [
            '#!' + params.location,
            // 'category', props.name,
            'clusters', props.clusterId
          ].join('/');
        return hash;
      })
      .classed('cluster', true)
      .style('fill', function(feature) {
        return colors[feature.properties.name];
      })
      .style('stroke', function(feature) {
        return colors[feature.properties.name];
      })
      .on("mouseover", function(d) {
        var data = {
          name: d.properties.name,
          idCluster: d.properties.idCluster,
          venuesLen: d.properties.venues.length
        }
        tooltip.showTooltip(infoTemplate({ data: data }), d3.event);
      })
      .on("mouseout", tooltip.hideTooltip);

    clustersSelection.exit().remove();
  }

  function updateVenuesSelection(selectedFeature) {
    var
      infoTemplate = _.template(d3.select('#venue-tooltip').html()),
      venues = [],
      maxBeenHere = _(venues).pluck('beenHere')[0],
      radiusScale;

    if (selectedFeature.features.length === 1) {
      venues = getVenues(selectedFeature);
    }

    var radiusScale = d3.scale.pow()
      .exponent(0.5)
      .domain([0, maxBeenHere])
      .range([minCircle, maxCircle]);

    // venuesSelection svg circle elements
    // representing venues
    venuesSelection = g.selectAll('circle')
      .data(venues, function(venue) {
        return venue.id;
      });

    venuesSelection.enter().append('circle')
      .attr('r', function(d) {
        return radiusScale(d.beenHere) || 1;
      })
      .on("mouseover", function(d) {
        d.date = moment(d.timestamp).fromNow();
        tooltip.showTooltip(infoTemplate({ data: d }), d3.event);
      })
      .on("mouseout", tooltip.hideTooltip)
      .on('click', function(d) {
        window.open(
          'https://foursquare.com/v/' + d.id,
          '_blank'
        );
      });

    venuesSelection.exit().remove();
  }

  function update(feature, params) {
    selectedFeature = feature;

    updateClustersSelection(selectedFeature, params);
    updateVenuesSelection(selectedFeature);

    onViewReset();
    map.fitBounds(getAllCoords(selectedFeature).map(app.utils.inv));
  }

  function onViewReset() {

    initPath();

    var bounds = getBounds(selectedFeature),
      topLeft = bounds[0],
      bottomRight = bounds[1],
        padding = Math.pow(maxCircle, map.getZoom() / 10);

    svg .attr("width", bottomRight[0] - topLeft[0] + (2 * padding))
        .attr("height", bottomRight[1] - topLeft[1] + (2 * padding))
        .style("left", (topLeft[0] - padding) + "px")
        .style("top", (topLeft[1] - padding) + "px");

    g  .attr("transform", "translate(" + (-topLeft[0] + padding) + "," + (-topLeft[1] + padding) + ")");

    clustersSelection.attr("d", function(feature) {
      var geo = feature.geometry.geometries[1];
      var coords = geo.coordinates[0];
      coords.push(coords[0]);
      geo.coordinates = [coords];

      return path(geo);
    }.bind(this));

    venuesSelection.each(function(venue) {
      var point = map.latLngToLayerPoint(new L.LatLng(venue.venueLatitude, venue.venueLongitude));
      d3.select(this)
        .attr('cx', point.x)
        .attr('cy', point.y);
    });

    // removePoints();
    // removeVenues();

    // show only if one cluster
    if (selectedFeature.features.length === 1) {

      // addPoints();
      // addVenues();
    }
  }

  function addPoints(limit, everyNth) {

    var limit = limit || 1000;
    var everyNth = everyNth || 2;

    var markers = _(selectedFeature.features.map(function(feature) {
      var coordinates = feature.geometry.geometries[0].coordinates;
      return coordinates.map(app.utils.inv)
        .filter(function(coords, i) { return i % everyNth === 0; })
        .slice(0, limit)
        .map(function(coords) {
          return L.marker(coords, { icon: L.divIcon({ className: 'grid-dot' }) });
        });
    })).flatten();

    layerPoints = L.layerGroup(markers).addTo(map);
  }

  function removePoints(argument) {
    if (layerPoints) { map.removeLayer(layerPoints); }
  }

  function getVenues(selectedFeature, limit) {

    limit = limit || 300;

    var coords, marker,
        markers = [];

    var feature = selectedFeature.features[0];
    var categoriesToShow = categoriesFromFeature(feature.properties.name);
    var venues = selectedFeature.features[0].properties.venues;

    var venuesInCategory = venues.filter(function(venue) {
      return (categoriesToShow.indexOf(venue.category) >= 0);
    });

    // if there are no venues that share the same categories as the cluster
    // show all the venues
    if (venuesInCategory.length > 0) {
      venues = venuesInCategory;
    }

    return venues.sort(sortVenue)
      .slice(0, limit);

    function sortVenue(v1, v2) {
      // beenHere is sometimes undefined!
      // this caused sort to fail, that led to huge circle bug
      return (v2.beenHere || 0) - (v1.beenHere || 0);
    }

    function categoriesFromFeature(categoryName) {
      var regex = /c\((.*?)\)/;
      var matched = regex.exec(categoryName);

      if (matched) {
        return matched[1].split(',');
      }
      return null;
    }
  }

  return {
        locationParams: locationParams,
    update: update,
    get: function() {
      return map;
    }
  }
};<|endoftext|>


<commit_before>var activationMap = new Array();
var linkMap = new Array();
var activationOrderMap = new Array();
var colorMap = new Array();

var currentNumberIndex = 1;//0;

$(document).ready(function()
{
  $("td[title]").tooltip({
    content: function () {
        var arrContent = $(this).attr('title').split(' - ');
        var data = $(this).attr('title');
        if(arrContent.length >= 2) {
          data = '
' + arrContent[0] + '

' + arrContent[1] + '
';
        }
        return data;
    }
  });
});

function getRGBFromNumber(i)
{
  var r = (((i)*224) % 255);
  var g = (((i + 197)*1034345) % 255);
  var b = (((i + 23)*74353) % 255);

  //  too dark or too bright?
  if(((r+b+g) / 3) < 100 )
  {
    r = 255 - r;
    g = 255 - g;
    b = 255 - b;
  }
  else if(((r+b+g) / 3) > 192 )
  {
    r = 1*(r / 2);
    g = 1*(g / 2);
    b = 1*(b / 2);
  }

  if(r == 255 && g == 255 && b == 255)
  {
    r = 255;
    g = 255;
    b = 0;
  }

  return "rgb(" + r + "," + g + "," + b + ")";
}

function togglePRAuto(el)
{

  togglePRAutoWithClass($(el), "highlightedToken");
}

function togglePRAutoWithClass(element, className)
{
  var idAtt = element.attr("annis:pr_right");
  var isOn = linkMap[idAtt]==1;
  if (linkMap[idAtt]==1) {
      linkMap[idAtt]=-1;
  } else {
      linkMap[idAtt]=1;
  }
  currentNumberIndex++;
  togglePRWithClass(element, !isOn, className);
}

function togglePRWithClass(element, on, className)
{
  if(element != null)
  {
    var attLeft = element.attr("annis:pr_left");
    if(attLeft != null)
    {
      var prIDsL = attLeft.split(',');

      $.each(prIDsL, function(index, pr)
      {
        var elToken = $("#tok_" + pr);
        togglePRWithClassFinal(elToken, on, className, element);
      });
    }

  }

}

function togglePRWithClassFinal(element, on, className, superEl)
{

  var idAtt = element.attr("id");

  var sElement = superEl;
  var superIdAtt = sElement.attr("annis:pr_right");

  if(element != null && idAtt != null)
  {

    var id = idAtt.substring("tok_".length);

    if(on)
    {
        element.addClass(className);
        if (activationOrderMap[idAtt] == null){
            activationOrderMap[idAtt] = "";
        }
        activationOrderMap[idAtt] += superIdAtt+",";
        colorMap[superIdAtt] = currentNumberIndex;
        element.attr("style", "background-color:" + getRGBFromNumber(currentNumberIndex));
        activationMap[id] = 1;
    }
    else
    {
      element.removeClass(className);
      var recolor = 0;
      if (activationOrderMap[idAtt]!=null) {
          activationOrderMap[idAtt] = activationOrderMap[idAtt].replace(superIdAtt+",","");
          var text = activationOrderMap[idAtt].split(",",10);
          for(var i=0;i<10;i++){
              var word = text[i];
              if (word != null)
              if (word.length>0) {
                    recolor = word;break;
              }
          }
      }
      if (recolor==0){
        element.attr("style" ,"background-color: rgb(255,255,255)");
        activationMap[id] = -1;
      } else {
        element.attr("style", "background-color:" + getRGBFromNumber(colorMap[recolor]));//colorMap[superIdAtt]));//"rgb(150,255,150)");
      }
    }
  }
}<commit_msg>Show tooltip in coref immediatly and remove the "showBody" hack (it isn't used in the java side of the code any longer)<commit_after>var activationMap = new Array();
var linkMap = new Array();
var activationOrderMap = new Array();
var colorMap = new Array();

var currentNumberIndex = 1;//0;

$(document).ready(function()
{
  $("td[title]").tooltip({
    // no animation
    show: false,
    content: function () {
        return $(this).attr('title');
    }
  });
});

function getRGBFromNumber(i)
{
  var r = (((i)*224) % 255);
  var g = (((i + 197)*1034345) % 255);
  var b = (((i + 23)*74353) % 255);

  //  too dark or too bright?
  if(((r+b+g) / 3) < 100 )
  {
    r = 255 - r;
    g = 255 - g;
    b = 255 - b;
  }
  else if(((r+b+g) / 3) > 192 )
  {
    r = 1*(r / 2);
    g = 1*(g / 2);
    b = 1*(b / 2);
  }

  if(r == 255 && g == 255 && b == 255)
  {
    r = 255;
    g = 255;
    b = 0;
  }

  return "rgb(" + r + "," + g + "," + b + ")";
}

function togglePRAuto(el)
{

  togglePRAutoWithClass($(el), "highlightedToken");
}

function togglePRAutoWithClass(element, className)
{
  var idAtt = element.attr("annis:pr_right");
  var isOn = linkMap[idAtt]==1;
  if (linkMap[idAtt]==1) {
      linkMap[idAtt]=-1;
  } else {
      linkMap[idAtt]=1;
  }
  currentNumberIndex++;
  togglePRWithClass(element, !isOn, className);
}

function togglePRWithClass(element, on, className)
{
  if(element != null)
  {
    var attLeft = element.attr("annis:pr_left");
    if(attLeft != null)
    {
      var prIDsL = attLeft.split(',');

      $.each(prIDsL, function(index, pr)
      {
        var elToken = $("#tok_" + pr);
        togglePRWithClassFinal(elToken, on, className, element);
      });
    }

  }

}

function togglePRWithClassFinal(element, on, className, superEl)
{

  var idAtt = element.attr("id");

  var sElement = superEl;
  var superIdAtt = sElement.attr("annis:pr_right");

  if(element != null && idAtt != null)
  {

    var id = idAtt.substring("tok_".length);

    if(on)
    {
        element.addClass(className);
        if (activationOrderMap[idAtt] == null){
            activationOrderMap[idAtt] = "";
        }
        activationOrderMap[idAtt] += superIdAtt+",";
        colorMap[superIdAtt] = currentNumberIndex;
        element.attr("style", "background-color:" + getRGBFromNumber(currentNumberIndex));
        activationMap[id] = 1;
    }
    else
    {
      element.removeClass(className);
      var recolor = 0;
      if (activationOrderMap[idAtt]!=null) {
          activationOrderMap[idAtt] = activationOrderMap[idAtt].replace(superIdAtt+",","");
          var text = activationOrderMap[idAtt].split(",",10);
          for(var i=0;i<10;i++){
              var word = text[i];
              if (word != null)
              if (word.length>0) {
                    recolor = word;break;
              }
          }
      }
      if (recolor==0){
        element.attr("style" ,"background-color: rgb(255,255,255)");
        activationMap[id] = -1;
      } else {
        element.attr("style", "background-color:" + getRGBFromNumber(colorMap[recolor]));//colorMap[superIdAtt]));//"rgb(150,255,150)");
      }
    }
  }
}<|endoftext|>


<commit_before>/**
 * # http.js
 * Copyright(c) 2014 Stefano Balietti
 * MIT Licensed
 *
 * Configuration file for Express server in nodegame-server
 * ---
 */
module.exports = configure;

// ## Global scope

var util = require('util'),
fs = require('fs'),
path = require('path'),
express = require('express'),
J = require('nodegame-client').JSUS,
nodemailer = require('nodemailer');

/**
 * ### ServerNode._configureHTTP
 *
 * Defines standard routes for the HTTP server
 *
 * @param {object} options The object containing the custom settings
 */
function configure(app, servernode) {
    var rootDir;
    var gameCachePublic = {};
    var gameCacheTemplate = {};
    var gameCacheContext = {};

    rootDir = servernode.rootDir;

    function verifyGameRequest(req, res) {
        var gameInfo;
        gameInfo = servernode.info.games[req.params.game];

        if (!gameInfo) {
            res.send('Resource ' + req.params.game + ' is not available.');
            return false;
        }

        if (!gameInfo.dir) {
            res.send('Resource ' + req.params.game +
                     ' is not configured properly: missing game directory.');
            return false;
        }

        return gameInfo;
    }

    function sendFromPublic(type, req, res) {
        var path, i;
        if (!req.params.file) return;
        if (req.params.file.lastIndexOf('\/') === (req.params.file.length-1)) {
            req.params.file = req.params.file.substring(0,req.params.file.length-1);
        }

        // Build path to file.
        path = rootDir + '/public/' + type + '/' + req.params.file;
        // Send file.
        res.sendfile(path);
    }

    // TODO: COMMENT
    function createAndServe(langPath, pageName, file,
                            gameInfo, contextPath, res) {
        var jadeTemplate, jsonContext;

        langPath = file.split('/')[1] + '/';
        pageName = file.split('/')[2].split('.')[0];

        jadeTemplate = gameInfo.dir + 'view/templates/' + pageName +
            '.jade';

        contextPath = gameInfo.dir + 'view/context/' + langPath +
            pageName + '.json';
        try {
            jsonContext = require(contextPath);
        }
        catch (e) {
            // redirect to 'page not found'
            res.sendfile(gameInfo.dir + 'public/notFound.html'); //TODO: INSERT PROPER FILE
            return;

        }
        res.render(jadeTemplate, jsonContext);
    }

    function renderTemplate(res, gameName, templatePath, contextPath,
                            gameCacheContext) {
        var context;
        debugger
        context = gameCacheContext[gameName][contextPath];

        if (context) {
            // TODO can modify context.
            res.render(templatePath, context);
            return;
        }

        fs.exists(contextPath, function(exists) {
            if (exists) {
                try {
                    context = require(contextPath);
                }
                catch(e) {
                    // TODO: Log error.
                    // TODO: Mark file as non-existig ?
                    // TODO: INSERT PROPER FILE.
                    res.sendfile(gameInfo.dir + 'public/notFound.html');
                    return;
                }
                gameCacheContext[gameName][contextPath] = context;
            }
            // Render anyway, with or without context.
            res.render(templatePath, context);
        });
    }

    app.use(express.cookieParser());

    app.configure('development', function(){
        app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
    });

    app.configure('production', function(){
        app.use(express.errorHandler());
    });

    app.enable("jsonp callback");

    app.get('/', function(req, res) {
        var q;
        if (J.isEmpty(req.query)) {
            res.render('index', {
                title: 'Yay! Your nodeGame server is running.'
            });
        }

        q = req.query.q;

        if (!q) {
            res.send('Query must start with q=XXX');
        }

        switch(q) {
        case 'info':
            //console.log(servernode.info);
            res.send(servernode.info);
            break;

        case 'channels':
            //console.log(servernode.info);
            res.send(servernode.info.channels);
            break;

        case 'games':
            //console.log(servernode.info);
            res.send(servernode.info.games);
            break;
        default:
            res.send('Unknown query received.');
        }
    });

    app.get('/error/:type', function(req, res) {
        var type = req.params.type;
        res.render('error/' + type);
    });

    app.get('/images/:file', function(req, res) {
        sendFromPublic('images', req, res);
    });

    app.get('/javascripts/:file', function(req, res) {
        sendFromPublic('javascripts', req, res);
    });

    app.get('/stylesheets/:file', function(req, res) {
        sendFromPublic('stylesheets', req, res);
    });

    app.get('/pages/:file', function(req, res) {
        sendFromPublic('pages', req, res);
    });

    // Serves game files or default game index file: index.htm.
    app.get('/:game/*', function(req, res) {
        var gameName, gameInfo, filePath, file;
        var jadeTemplate, jsonContext, contextPath, langPath, pageName;

        gameInfo = verifyGameRequest(req, res);
        if (!gameInfo) return;

        gameName = req.params.game;
        file = req.params[0];

        // TODO: place in a proper place.
        if (!gameCachePublic[gameName]) gameCachePublic[gameName] = {};
        if (!gameCacheTemplate[gameName]) gameCacheTemplate[gameName] = {};
        if (!gameCacheContext[gameName]) gameCacheContext[gameName] = {};

        if ('' === file || 'undefined' === typeof file) {
            file = 'index.htm';
        }
        else if (file.lastIndexOf('\/') === (file.length-1)) {
            // Removing the trailing slash because it creates:
            // Error: ENOTDIR in fetching the file.
            file = file.substring(0, file.length-1);
        }

        // TODO: fix this.
        // Executes callback if one has been defined for this file.
        if (app.gameHooks) {
            for (i = 0; i < app.gameHooks.length; ++i) {
                if (app.gameHooks[i].file === file) {
                    app.gameHooks[i].callback(req, res);
                    return;
                }
            }
        }

        // Build filePath to file in public directory.
        filePath = gameInfo.dir + 'public/' + file;
        console.log(file);

        // TODO: move after refactoring.
        // Send JSON data as JSONP if there is a callback query parameter:
        if (file.match(/\.json$/) && req.query.callback) {
            // Load file contents:
            fs.readFile(filePath, 'utf8', function(err, data) {
                var callback;

                if (err) {
                    res.json({error: 'file not found'}, 404);
                    return;
                }

                // Send it:
                res.header('Content-Type', 'application/javascript');
                res.header('Charset', 'utf-8');
                res.send(req.query.callback + '(' + data + ');');
            });

            return;
        }

        // If file is in public/ serve it immediately (cached by Express).
        if (gameCachePublic[gameName] && gameCachePublic[gameName][file]) {
            // Send file (if it is a directory it is not sent).
            res.sendfile(filePath);
            return;
        }

        // If exists in 'public/', cache it and serve it.
        fs.exists(filePath, function(exists) {
            var basename, templatePath, contextPath, context;
            if (exists) {
                fs.readFile(filePath, 'utf8', function(err, data) {
                    gameCachePublic[gameName][file] = true;
                    res.sendfile(filePath);
                });
                return;
            }
            debugger
            // Check if it a template.
            basename = file.substr(0, file.lastIndexOf('.'));
            templatePath = gameInfo.dir + 'views/templates/' +
                basename + '.jade';
            contextPath = gameInfo.dir + 'views/contexts/' +
                basename + '.json';

            // Template not existing.
            if (gameCacheTemplate[gameName][templatePath] === false) {
                res.sendfile(gameInfo.dir + 'public/notFound.html');
            }
            // Template existing, render it.
            else if (gameCacheTemplate[gameName][templatePath] === true) {
                renderTemplate(res, gameName, templatePath, contextPath,
                               gameCacheContext);
            }
            // Do not know if exists or not, check and render it.
            else if ('undefined' === typeof gameCacheTemplate[gameName][templatePath]) {

                fs.exists(templatePath, function(exists) {
                    debugger
                    if (!exists) {
                        gameCacheTemplate[gameName][templatePath] = false;
                        res.sendfile(gameInfo.dir + 'public/notFound.html');
                        return;
                    }
                    gameCacheTemplate[gameName][templatePath] = true;
                    renderTemplate(res, gameName, templatePath, contextPath,
                                   gameCacheContext);
                });
            }
        });

        return;

        // Instantiate templates, if needed and available.
        // `html/templates/page.jade` holds the template and
        // `html/context/lang/page.json` holds the context to instantiate
        // the page requested by `html/lang/page*`.
        if (file.match(/^[^\/]*\/.*$/)) {
            // Check whether existance of page has been assertained before.
            if ('undefined' === typeof gameCachePublic[filePath]) {
                fs.exists(filePath, function(exists) {
                    gameCachePublic[filePath] = exists;
                    if (exists) {
                        res.sendfile(filePath);
                    }
                    else {
                        createAndServe(langPath, pageName, file, gameInfo,
                            contextPath, res);
                    }
                });
                return;
            }
            // If it is known that page doesn't exist, render it from template.
            if (!gameCachePublic[filePath]) {
                createAndServe(langPath, pageName, file, gameInfo, contextPath,
                    res);
                return;
            }
        }

        // Send file (if it is a directory it is not sent).
        //res.sendfile(path.basename(filePath), {root: path.dirname(filePath)});
        res.sendfile(filePath);

    });

    app.get('/:game', function(req, res) {
        res.redirect('/' + req.params.game + '/');
    });

    app.configure(function(){
        app.set('views', rootDir + '/views');
        app.set('view engine', 'jade');
        app.set('view options',{layout: false});
        app.use(express.static(rootDir + '/public'));
    });

    return true;
}
<commit_msg>kind of work. still better handling is needed<commit_after>/**
 * # http.js
 * Copyright(c) 2014 Stefano Balietti
 * MIT Licensed
 *
 * Configuration file for Express server in nodegame-server
 * ---
 */
module.exports = configure;

// ## Global scope

var util = require('util'),
fs = require('fs'),
path = require('path'),
express = require('express'),
J = require('nodegame-client').JSUS,
nodemailer = require('nodemailer');

/**
 * ### ServerNode._configureHTTP
 *
 * Defines standard routes for the HTTP server
 *
 * @param {object} options The object containing the custom settings
 */
function configure(app, servernode) {
    var rootDir;
    var gameCachePublic = {};
    var gameCacheTemplate = {};
    var gameCacheContext = {};

    rootDir = servernode.rootDir;

    function verifyGameRequest(req, res) {
        var gameInfo;
        gameInfo = servernode.info.games[req.params.game];

        if (!gameInfo) {
            res.send('Resource ' + req.params.game + ' is not available.');
            return false;
        }

        if (!gameInfo.dir) {
            res.send('Resource ' + req.params.game +
                     ' is not configured properly: missing game directory.');
            return false;
        }

        return gameInfo;
    }

    function sendFromPublic(type, req, res) {
        var path, i;
        if (!req.params.file) return;
        if (req.params.file.lastIndexOf('\/') === (req.params.file.length-1)) {
            req.params.file = req.params.file.substring(0,req.params.file.length-1);
        }

        // Build path to file.
        path = rootDir + '/public/' + type + '/' + req.params.file;
        // Send file.
        res.sendfile(path);
    }

    // TODO: COMMENT
    function createAndServe(langPath, pageName, file,
                            gameInfo, contextPath, res) {
        var jadeTemplate, jsonContext;

        langPath = file.split('/')[1] + '/';
        pageName = file.split('/')[2].split('.')[0];

        jadeTemplate = gameInfo.dir + 'view/templates/' + pageName +
            '.jade';

        contextPath = gameInfo.dir + 'view/context/' + langPath +
            pageName + '.json';
        try {
            jsonContext = require(contextPath);
        }
        catch (e) {
            // redirect to 'page not found'
            res.sendfile(gameInfo.dir + 'public/notFound.html'); //TODO: INSERT PROPER FILE
            return;

        }
        res.render(jadeTemplate, jsonContext);
    }

    function renderTemplate(res, gameName, templatePath, contextPath,
                            gameCacheContext) {
        var context;

        context = gameCacheContext[gameName][contextPath];

        if (context) {
            // TODO can modify context.
            console.log('----------------------------------!!!!!!!!');
            res.render(templatePath, context);
            return;
        }

        fs.exists(contextPath, function(exists) {
            if (exists) {
                try {
                    context = require(contextPath);
                }
                catch(e) {
                    // TODO: Log error.
                    // TODO: Mark file as non-existig ?
                    // TODO: INSERT PROPER FILE.
                    res.sendfile(gameInfo.dir + 'public/notFound.html');
                    return;
                }
                gameCacheContext[gameName][contextPath] = context;
            }
            debugger
            console.log('----------------------------------!!!!!!!!2');
            // Render anyway, with or without context.
            res.render(templatePath, context);
        });
    }

    app.use(express.cookieParser());

    app.configure('development', function(){
        app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
    });

    app.configure('production', function(){
        app.use(express.errorHandler());
    });

    app.enable("jsonp callback");

    app.get('/', function(req, res) {
        var q;
        if (J.isEmpty(req.query)) {
            res.render('index', {
                title: 'Yay! Your nodeGame server is running.'
            });
        }

        q = req.query.q;

        if (!q) {
            res.send('Query must start with q=XXX');
        }

        switch(q) {
        case 'info':
            //console.log(servernode.info);
            res.send(servernode.info);
            break;

        case 'channels':
            //console.log(servernode.info);
            res.send(servernode.info.channels);
            break;

        case 'games':
            //console.log(servernode.info);
            res.send(servernode.info.games);
            break;
        default:
            res.send('Unknown query received.');
        }
    });

    app.get('/error/:type', function(req, res) {
        var type = req.params.type;
        res.render('error/' + type);
    });

    app.get('/images/:file', function(req, res) {
        sendFromPublic('images', req, res);
    });

    app.get('/javascripts/:file', function(req, res) {
        sendFromPublic('javascripts', req, res);
    });

    app.get('/stylesheets/:file', function(req, res) {
        sendFromPublic('stylesheets', req, res);
    });

    app.get('/pages/:file', function(req, res) {
        sendFromPublic('pages', req, res);
    });

    // Serves game files or default game index file: index.htm.
    app.get('/:game/*', function(req, res) {
        var gameName, gameInfo, filePath, file;
        var jadeTemplate, jsonContext, contextPath, langPath, pageName;

        gameInfo = verifyGameRequest(req, res);
        if (!gameInfo) return;

        gameName = req.params.game;
        file = req.params[0];

        // TODO: place in a proper place.
        if (!gameCachePublic[gameName]) gameCachePublic[gameName] = {};
        if (!gameCacheTemplate[gameName]) gameCacheTemplate[gameName] = {};
        if (!gameCacheContext[gameName]) gameCacheContext[gameName] = {};

        if ('' === file || 'undefined' === typeof file) {
            file = 'index.htm';
        }
        else if (file.lastIndexOf('\/') === (file.length-1)) {
            // Removing the trailing slash because it creates:
            // Error: ENOTDIR in fetching the file.
            file = file.substring(0, file.length-1);
        }

        // TODO: fix this.
        // Executes callback if one has been defined for this file.
        if (app.gameHooks) {
            for (i = 0; i < app.gameHooks.length; ++i) {
                if (app.gameHooks[i].file === file) {
                    app.gameHooks[i].callback(req, res);
                    return;
                }
            }
        }

        // Build filePath to file in public directory.
        filePath = gameInfo.dir + 'public/' + file;
        console.log(file);

        // TODO: move after refactoring.
        // Send JSON data as JSONP if there is a callback query parameter:
        if (file.match(/\.json$/) && req.query.callback) {
            // Load file contents:
            fs.readFile(filePath, 'utf8', function(err, data) {
                var callback;

                if (err) {
                    res.json({error: 'file not found'}, 404);
                    return;
                }

                // Send it:
                res.header('Content-Type', 'application/javascript');
                res.header('Charset', 'utf-8');
                res.send(req.query.callback + '(' + data + ');');
            });

            return;
        }

        // If file is in public/ serve it immediately (cached by Express).
        if (gameCachePublic[gameName] && gameCachePublic[gameName][file]) {
            // Send file (if it is a directory it is not sent).
            res.sendfile(filePath);
            return;
        }

        // If exists in 'public/', cache it and serve it.
        fs.exists(filePath, function(exists) {
            var basename, templatePath, contextPath, context;
            if (exists) {
                fs.readFile(filePath, 'utf8', function(err, data) {
                    gameCachePublic[gameName][file] = true;
                    res.sendfile(filePath);
                });
                return;
            }

            // Check if it a template.
            basename = file.substr(0, file.lastIndexOf('.'));

            // conlangPath = file.split('/')[1] + '/';
            //pageName = ;

            // Instantiate templates, if needed and available.
            // `html/templates/page.jade` holds the template and
            // `html/context/lang/page.json` holds the context to instantiate
            // the page requested by `html/lang/page*`.

            // TODO: Matches too much: xx/xx/xx/xx/xx/
            if (basename.match(/^[^\/]*\/.*$/)) {
                templatePath = gameInfo.dir + 'views/templates/' +
                    basename.split('/')[1] + '.jade';
            }
            else {
                templatePath = gameInfo.dir + 'views/templates/' +
                    basename + '.jade';
            }

            contextPath = gameInfo.dir + 'views/contexts/' +
                basename + '.json';

            // Template not existing.
            if (gameCacheTemplate[gameName][templatePath] === false) {
                res.sendfile(gameInfo.dir + 'public/notFound.html');
            }
            // Template existing, render it.
            else if (gameCacheTemplate[gameName][templatePath] === true) {
                renderTemplate(res, gameName, templatePath, contextPath,
                               gameCacheContext);
            }
            // Do not know if exists or not, check and render it.
            else if ('undefined' === typeof gameCacheTemplate[gameName][templatePath]) {

                fs.exists(templatePath, function(exists) {
                    if (!exists) {
                        gameCacheTemplate[gameName][templatePath] = false;
                        res.sendfile(gameInfo.dir + 'public/notFound.html');
                        return;
                    }
                    gameCacheTemplate[gameName][templatePath] = true;
                    renderTemplate(res, gameName, templatePath, contextPath,
                                   gameCacheContext);
                });
            }
        });

        return;

        // Send file (if it is a directory it is not sent).
        //res.sendfile(path.basename(filePath), {root: path.dirname(filePath)});
        res.sendfile(filePath);

    });

    app.get('/:game', function(req, res) {
        res.redirect('/' + req.params.game + '/');
    });

    app.configure(function(){
        app.set('views', rootDir + '/views');
        app.set('view engine', 'jade');
        app.set('view options',{layout: false});
        app.use(express.static(rootDir + '/public'));
    });

    return true;
}
<|endoftext|>