<commit_before>const ClientModule = require('./ClientModule');
const ClientSpace = require('./ClientSpace');
const client = require('./client');
const EventEmitter = require('events').EventEmitter;

// display strategies for placer
class ListSelector extends EventEmitter {
  constructor(options) {
    this._indexPositionMap = new Map();
    this._onSelect = this._onSelect.bind(this);
  }

  _onSelect(e) {
    const options = this.select.options;
    const selectedIndex = this.select.selectedIndex;
    const index = parseInt(options[selectedIndex].value, 10);
    const position = this._indexPositionMap.get(index);
    this.emit('select', position);
  }

  display(setup, container, options = {}) {
    this.container = container;
    this.el = document.createElement('div');

    this.select = document.createElement('select');
    this.button = document.createElement('button');
    this.button.textContent = 'OK';
    this.button.classList.add('btn');

    this.el.appendChild(this.select);
    this.el.appendChild(this.button);
    this.container.appendChild(this.el);

    this.button.addEventListener('touchstart', this._onSelect, false);
    this.resize();
  }

  resize() {
    if (!this.container) { return; } // if called before `display`
    const containerWidth = this.container.getBoundingClientRect().width;
    const containerHeight = this.container.getBoundingClientRect().height;

    const height = this.el.getBoundingClientRect().height;
    const width = containerWidth * 2 / 3;
    const left = containerWidth / 3 / 2;
    const top = (containerHeight - height) / 2;

    this.el.style.position = 'absolute';
    this.el.style.width = width;
    this.el.style.top = top;
    this.el.style.left = left;

    this.select.style.width = width;
    this.button.style.width = width;
  }

  displayPositions(positions) {
    positions.forEach((position) => {
      const option = document.createElement('option');
      option.value = position.index;
      option.textContent = position.label;

      this.select.appendChild(option);
      this._indexPositionMap.set(position.index, position);
    });
  }
}

class ClientPlacer extends ClientModule {
  constructor(options = {}) {
    super(options.name || 'placer', true);

    this.setup = options.setup;
    this.mode = options.mode || 'list';
    this.persist = options.persist || false;
    this.localStorageId = options.localStorageId || 'soundworks';

    switch (this.mode) {
      case 'graphic':
        this.selector = new ClientSpace({
          fitContainer: true,
          listenTouchEvent: true,
        });
        break;
      case 'list':
        this.selector = new ListSelector({});
        break;
    }

    this._resizeSelector = this._resizeSelector.bind(this);
    window.addEventListener('resize', this._resizeSelector, false);
    // allow to reset localStorage
    client.receive(`${this.name}:reset`, this._deleteInformation);

    // DEBUG
    // this._deleteInformation();
  }

  _resizeSelector() {
    this.selector.resize();
  }

  _getStorageKey() {
    return `${this.localStorageId}:${this.name}`;
  }

  _persistInformation(position) {
    // if options.expire add th timestamp to the position object
    const key = this._getStorageKey();
    window.localStorage.setItem(key, JSON.stringify(position));
  }

  _retrieveInformation() {
    const key = this._getStorageKey();
    const position = window.localStorage.getItem(key);

    // check for expires entry
    // delete if now > expires
    return JSON.parse(position);
  }

  _deleteInformation() {
    const key = this._getStorageKey();
    window.localStorage.removeItem(key);
    // window.localStorage.clear(); // remove everything for the domain
  }

  _sendInformation(position = null) {
    if (position !== null) {
      this.index = position.index;
      this.label = position.label;
      client.coordinates = position.coordinates;
    }

    client.send(
      `${this.name}:information`,
      this.index,
      this.label,
      client.coordinates
    );
  }

  start() {
    super.start();

    // prepare positions
    this.positions = this.setup.coordinates.map((coord, index) => {
      return {
        index: index,
        label: this.setup.labels[index],
        coordinates: coord,
      };
    });

    // check for informations in local storage
    if (this.persist) {
      const position = this._retrieveInformation();

      if (position !== null) {
        this._sendInformation(position);
        return this.done();
      }
    }

    // listen for selection
    this.selector.on('select', (position) => {
      // optionally store in local storage
      if (this.persist) {
        this._persistInformation(position);
      }
      // send to server
      this._sendInformation(position);
      this.done();
    });

    this.selector.display(this.setup, this.view, {});
    // make sure the DOM is ready (needed on ipods)
    setTimeout(() => {
      this.selector.displayPositions(this.positions, 20);
    }, 0);
  }

  restart() {
    super.restart();
    this._sendInformation();
  }

  reset() {
    super.reset();
    // reset client
    this.index = null;
    this.label = null;
    client.coordinates = null;
    // remove listener
    this.selector.removeAllListeners('select');
  }

  done() {
    super.done();
    window.removeEventListener('resize', this._resizeSelector, false);
    this.selector.removeAllListeners('select');
  }
}

module.exports = ClientPlacer;
<commit_msg>fixed ListSelector css<commit_after>const ClientModule = require('./ClientModule');
const ClientSpace = require('./ClientSpace');
const client = require('./client');
const EventEmitter = require('events').EventEmitter;

// display strategies for placer
class ListSelector extends EventEmitter {
  constructor(options) {
    this._indexPositionMap = new Map();
    this._onSelect = this._onSelect.bind(this);
  }

  _onSelect(e) {
    const options = this.select.options;
    const selectedIndex = this.select.selectedIndex;
    const index = parseInt(options[selectedIndex].value, 10);
    const position = this._indexPositionMap.get(index);
    this.emit('select', position);
  }

  display(setup, container, options = {}) {
    this.container = container;
    this.el = document.createElement('div');

    this.select = document.createElement('select');
    this.button = document.createElement('button');
    this.button.textContent = 'OK';
    this.button.classList.add('btn');

    this.el.appendChild(this.select);
    this.el.appendChild(this.button);
    this.container.appendChild(this.el);

    this.button.addEventListener('touchstart', this._onSelect, false);
    this.resize();
  }

  resize() {
    if (!this.container) { return; } // if called before `display`

    const containerWidth = this.container.getBoundingClientRect().width;
    const containerHeight = this.container.getBoundingClientRect().height;

    const height = this.el.getBoundingClientRect().height;
    const width = containerWidth * 2 / 3;
    const left = containerWidth / 3 / 2;
    const top = (containerHeight - height) / 2;

    this.el.style.position = 'absolute';
    this.el.style.width = width + 'px';
    this.el.style.top = top + 'px';
    this.el.style.left = left + 'px';

    this.select.style.width = width + 'px';
    this.button.style.width = width + 'px';
  }

  displayPositions(positions) {
    positions.forEach((position) => {
      const option = document.createElement('option');
      option.value = position.index;
      option.textContent = position.label;

      this.select.appendChild(option);
      this._indexPositionMap.set(position.index, position);
    });
  }
}

class ClientPlacer extends ClientModule {
  constructor(options = {}) {
    super(options.name || 'placer', true);

    this.setup = options.setup;
    this.mode = options.mode || 'list';
    this.persist = options.persist || false;
    this.localStorageId = options.localStorageId || 'soundworks';

    switch (this.mode) {
      case 'graphic':
        this.selector = new ClientSpace({
          fitContainer: true,
          listenTouchEvent: true,
        });
        break;
      case 'list':
        this.selector = new ListSelector({});
        break;
    }

    this._resizeSelector = this._resizeSelector.bind(this);
    window.addEventListener('resize', this._resizeSelector, false);
    // allow to reset localStorage
    client.receive(`${this.name}:reset`, this._deleteInformation);

    // DEBUG
    // this._deleteInformation();
  }

  _resizeSelector() {
    this.selector.resize();
  }

  _getStorageKey() {
    return `${this.localStorageId}:${this.name}`;
  }

  _persistInformation(position) {
    // if options.expire add th timestamp to the position object
    const key = this._getStorageKey();
    window.localStorage.setItem(key, JSON.stringify(position));
  }

  _retrieveInformation() {
    const key = this._getStorageKey();
    const position = window.localStorage.getItem(key);

    // check for expires entry
    // delete if now > expires
    return JSON.parse(position);
  }

  _deleteInformation() {
    const key = this._getStorageKey();
    window.localStorage.removeItem(key);
    // window.localStorage.clear(); // remove everything for the domain
  }

  _sendInformation(position = null) {
    if (position !== null) {
      this.index = position.index;
      this.label = position.label;
      client.coordinates = position.coordinates;
    }

    client.send(
      `${this.name}:information`,
      this.index,
      this.label,
      client.coordinates
    );
  }

  start() {
    super.start();

    // prepare positions
    this.positions = this.setup.coordinates.map((coord, index) => {
      return {
        index: index,
        label: this.setup.labels[index],
        coordinates: coord,
      };
    });

    // check for informations in local storage
    if (this.persist) {
      const position = this._retrieveInformation();

      if (position !== null) {
        this._sendInformation(position);
        return this.done();
      }
    }

    // listen for selection
    this.selector.on('select', (position) => {
      // optionally store in local storage
      if (this.persist) {
        this._persistInformation(position);
      }
      // send to server
      this._sendInformation(position);
      this.done();
    });

    this.selector.display(this.setup, this.view, {});
    // make sure the DOM is ready (needed on ipods)
    setTimeout(() => {
      this.selector.displayPositions(this.positions, 20);
    }, 0);
  }

  restart() {
    super.restart();
    this._sendInformation();
  }

  reset() {
    super.reset();
    // reset client
    this.index = null;
    this.label = null;
    client.coordinates = null;
    // remove listener
    this.selector.removeAllListeners('select');
  }

  done() {
    super.done();
    window.removeEventListener('resize', this._resizeSelector, false);
    this.selector.removeAllListeners('select');
  }
}

module.exports = ClientPlacer;
<|endoftext|>


<commit_before>'use strict'

import React from 'react'
import {connect} from 'react-redux'

import Header from '../components/header'
import Footer from '../components/footer'
import Sidebar from '../components/sidebar'

import {toggleSidebar} from '../actions/sidebar'

export const Shell = React.createClass({
  handleToggle (event) {
    event.preventDefault()
    this.props.dispatch(toggleSidebar())
  },

  render () {
    const handleToggle = this.handleToggle
    const {children, isLoggedIn, isExpanded} = this.props

    var styleClass = 'page-wrapper'
    if (isLoggedIn & isExpanded) styleClass = `${styleClass}-expanded`

    let sidebar = null
    if (isLoggedIn) {
      sidebar = <Sidebar isExpanded={isExpanded} onToggle={handleToggle} />
    }

    return (

          {sidebar}
          {children}

    )
  }
})

export default connect((state) => {

  return {
    isLoggedIn: state.auth.isLoggedIn,
    isExpanded: state.sidebar.isExpanded
  }
})(Shell)
<commit_msg>unbreak the full page behavior on log in<commit_after>'use strict'

import React from 'react'
import {connect} from 'react-redux'

import Header from '../components/header'
import Footer from '../components/footer'
import Sidebar from '../components/sidebar'

import {toggleSidebar} from '../actions/sidebar'

export const Shell = React.createClass({
  handleToggle (event) {
    event.preventDefault()
    this.props.dispatch(toggleSidebar())
  },

  render () {
    const handleToggle = this.handleToggle
    const {children, isLoggedIn, isExpanded} = this.props

    var styleClass = 'page-wrapper'
     if (isLoggedIn && isExpanded) {
       styleClass = `${styleClass}-expanded`
     }
     if (!isLoggedIn) {
       styleClass = `${styleClass}-full`
     }

    let sidebar = null
    if (isLoggedIn) {
      sidebar = <Sidebar isExpanded={isExpanded} onToggle={handleToggle} />
    }

    return (

          {sidebar}
          {children}

    )
  }
})

export default connect((state) => {

  return {
    isLoggedIn: state.auth.isLoggedIn,
    isExpanded: state.sidebar.isExpanded
  }
})(Shell)
<|endoftext|>


<commit_before>import Button from '@material-ui/core/Button';
import classNames from 'classnames';
import { compose, graphql } from 'react-apollo';
import ContentEditor from '../content/ContentEditor';
import { convertFromRaw, EditorState } from 'draft-js';
import Cookies from 'js-cookie';
import { defineMessages, F } from '../../shared/i18n';
import { EditorUtils } from 'hello-world-editor';
import FormControl from '@material-ui/core/FormControl';
import gql from 'graphql-tag';
import HiddenSnackbarShim from '../components/HiddenSnackbarShim';
import MenuItem from '@material-ui/core/MenuItem';
import React, { PureComponent } from 'react';
import Select from '@material-ui/core/Select';
import styles from './Dashboard.module.css';
import Toolbar from '@material-ui/core/Toolbar';

const messages = defineMessages({
  error: { msg: 'Error posting content.' },
  posted: { msg: 'Success!' },
});

class DashboardEditor extends PureComponent {
  constructor(props) {
    super(props);

    this.editor = React.createRef();

    this.state = {
      fileInfo: null,
      // Not so clean but, meh, don't feel like implementing two separate <select>'s
      sectionAndAlbum: JSON.stringify({ section: this.props.data.fetchSiteMap[0].name, album: '' }),
      errorMessage: null,
      successMessage: null,
    };
  }

  componentDidMount() {
    if (window.location.hash.startsWith('#reblog')) {
      const searchParams = new URLSearchParams(window.location.hash.slice(1));
      const url = searchParams.get('reblog');
      const img = searchParams.get('img');

      this.reblog(img || url);
      window.location.hash = '';
    }

    // On first load, set unsaved changes to false, the first change is just a focus selection change.
    setTimeout(() => {
      this.editor.current && this.editor.current.setUnsavedChanges(false);
    }, 0);

    const sectionAndAlbum = Cookies.get('sectionAndAlbum');
    if (sectionAndAlbum) {
      this.setState({ sectionAndAlbum });
    }

    document.addEventListener('keydown', this.handleKeyDown);
  }

  componentWillUnmount() {
    document.removeEventListener('keydown', this.handleKeyDown);
  }

  handleKeyDown = evt => {
    // TODO(mime): combine this logic somewhere. (also in keyboard.js)
    const isMac = navigator.platform.toLowerCase().indexOf('mac') !== -1;
    const isAccelKey = isMac ? evt.metaKey : evt.ctrlKey;
    if (isAccelKey && evt.key === 's') {
      this.handlePost();
    }
  };

  reblog(text) {
    const contentEditor = this.editor.current.getContentEditor();
    const editorState = contentEditor.editorState;
    EditorUtils.Text.insertTextAtLine(editorState, 0, '\n');
    contentEditor.handlePastedText(text, undefined /* html */, editorState);
  }

  handlePost = async evt => {
    evt && evt.preventDefault();

    const editor = this.editor.current;
    const { style, code, content } = editor.export();

    // TODO(mime): gotta be a simpler way then all this conversion.
    const title = EditorUtils.Text.getTextForLine(EditorState.createWithContent(convertFromRaw(content)), 0);

    const username = this.props.username;
    const name = title.replace(/[^A-Za-z0-9-]/g, '-');
    const { section, album } = JSON.parse(this.state.sectionAndAlbum);

    const thumb = this.state.fileInfo?.thumb || '';

    const variables = {
      username,
      section,
      album,
      name,
      title,
      hidden: false, // TODO(mime)
      thumb,
      style,
      code,
      content: JSON.stringify(content),
    };

    try {
      await this.props.mutate({
        variables,
        optimisticResponse: {
          __typename: 'Mutation',
          postContent: Object.assign({}, variables, { __typename: 'Content' }),
        },
      });
    } catch (ex) {
      this.setState({ errorMessage: messages.error }, () => {
        this.setState({ errorMessage: null });
      });
      return;
    }

    editor.clear();

    this.setState({ fileInfo: null, successMessage: messages.posted }, () => {
      this.setState({ successMessage: null });
    });
  };

  handleSectionAndAlbumChange = evt => {
    this.setState({ [evt.target.name]: evt.target.value });
    Cookies.set('sectionAndAlbum', evt.target.value);
  };

  generateSiteMapItem(item, albums, sectionName) {
    const value = {
      section: sectionName || item.name,
      album: sectionName ? item.name : '',
    };
    return (
      <MenuItem key={item.name} value={JSON.stringify(value)}>
        {!albums ? <>   </> : null}
        {item.title}
      </MenuItem>
    );
  }

  generateSiteMap(siteMap) {
    let items = [];
    for (let i = 0; i < siteMap.length; ++i) {
      const item = siteMap[i];
      const nextItem = siteMap[i + 1];

      let albums = [];
      if (nextItem?.album === 'main') {
        for (i += 1; i < siteMap.length; ++i) {
          const albumItem = siteMap[i];
          if (albumItem.album === 'main') {
            albums.push(this.generateSiteMapItem(albumItem, undefined, item.name));
          } else {
            i -= 1;
            break;
          }
        }
      }

      items.push(this.generateSiteMapItem(item, albums));
      items = items.concat(albums);
    }

    return items;
  }

  handleMediaAdd = fileInfos => {
    // We currently just grab the first one and disregard the other ones for purposes of getting the thumb.
    this.setState({ fileInfo: fileInfos[0] });
  };

  render() {
    return (

        <Toolbar>
          <form autoComplete="off" className={styles.editorForm}>
            <FormControl className={styles.editorToolbar}>
              <Select
                value={this.state.sectionAndAlbum}
                onChange={this.handleSectionAndAlbumChange}
                inputProps={{
                  name: 'sectionAndAlbum',
                }}
              >
                {this.generateSiteMap(this.props.data.fetchSiteMap)}
              </Select>
              <Button
                type="submit"
                onClick={this.handlePost}
                className={classNames('hw-button', 'hw-edit', styles.post)}
              >
                <F msg="post" />
              </Button>
            </FormControl>
          </form>
        </Toolbar>
        <ContentEditor
          ref={this.editor}
          showPlaceholder={true}
          mentions={this.props.data.fetchFollowing}
          content={null}
          onMediaAdd={this.handleMediaAdd}
        />
        <HiddenSnackbarShim
          message={this.state.successMessage || this.state.errorMessage}
          variant={this.state.successMessage ? 'success' : 'error'}
        />

    );
  }
}

export default compose(
  graphql(
    gql`
      query SiteMapAndUserQuery($username: String!) {
        fetchSiteMap(username: $username) {
          album
          hidden
          name
          section
          title
          username
        }

        fetchFollowing {
          name
          username
          profile_url
          avatar
          favicon
        }
      }
    `,
    {
      options: ({ username }) => ({
        variables: {
          username: username,
        },
      }),
      withRef: true,
    }
  ),
  graphql(
    gql`
      mutation postContent(
        $section: String!
        $album: String!
        $name: String!
        $title: String!
        $hidden: Boolean!
        $thumb: String!
        $style: String!
        $code: String!
        $content: String!
      ) {
        postContent(
          section: $section
          album: $album
          name: $name
          title: $title
          hidden: $hidden
          thumb: $thumb
          style: $style
          code: $code
          content: $content
        ) {
          username
          section
          album
          name
          title
          hidden
          thumb
          style
          code
          content
        }
      }
    `,
    { withRef: true }
  )
)(DashboardEditor);
<commit_msg>dashboard: fix up reblog function, add insertText function for editor<commit_after>import Button from '@material-ui/core/Button';
import classNames from 'classnames';
import { compose, graphql } from 'react-apollo';
import ContentEditor from '../content/ContentEditor';
import { convertFromRaw, EditorState } from 'draft-js';
import Cookies from 'js-cookie';
import { defineMessages, F } from '../../shared/i18n';
import { EditorUtils } from 'hello-world-editor';
import FormControl from '@material-ui/core/FormControl';
import gql from 'graphql-tag';
import HiddenSnackbarShim from '../components/HiddenSnackbarShim';
import MenuItem from '@material-ui/core/MenuItem';
import React, { PureComponent } from 'react';
import Select from '@material-ui/core/Select';
import styles from './Dashboard.module.css';
import Toolbar from '@material-ui/core/Toolbar';

const messages = defineMessages({
  error: { msg: 'Error posting content.' },
  posted: { msg: 'Success!' },
});

class DashboardEditor extends PureComponent {
  constructor(props) {
    super(props);

    this.editor = React.createRef();

    this.state = {
      fileInfo: null,
      // Not so clean but, meh, don't feel like implementing two separate <select>'s
      sectionAndAlbum: JSON.stringify({ section: this.props.data.fetchSiteMap[0].name, album: '' }),
      errorMessage: null,
      successMessage: null,
    };
  }

  componentDidMount() {
    if (window.location.hash.startsWith('#reblog')) {
      const searchParams = new URLSearchParams(window.location.hash.slice(1));
      const url = searchParams.get('reblog');
      const img = searchParams.get('img');

      this.reblog(img || url, url);
      window.location.hash = '';
    }

    // On first load, set unsaved changes to false, the first change is just a focus selection change.
    setTimeout(() => {
      this.editor.current && this.editor.current.setUnsavedChanges(false);
    }, 0);

    const sectionAndAlbum = Cookies.get('sectionAndAlbum');
    if (sectionAndAlbum) {
      this.setState({ sectionAndAlbum });
    }

    document.addEventListener('keydown', this.handleKeyDown);
  }

  componentWillUnmount() {
    document.removeEventListener('keydown', this.handleKeyDown);
  }

  handleKeyDown = evt => {
    // TODO(mime): combine this logic somewhere. (also in keyboard.js)
    const isMac = navigator.platform.toLowerCase().indexOf('mac') !== -1;
    const isAccelKey = isMac ? evt.metaKey : evt.ctrlKey;
    if (isAccelKey && evt.key === 's') {
      this.handlePost();
    }
  };

  async reblog(text, opt_url) {
    const contentEditor = this.editor.current.getContentEditor();
    let editorState = contentEditor.editorState;
    editorState = EditorUtils.Text.insertTextAtLine(editorState, 0, '\n');
    await contentEditor.handlePastedText(text, undefined /* html */, editorState);

    if (opt_url) {
      editorState = EditorUtils.Text.insertTextAtLine(contentEditor.editorState, 2, opt_url);
      contentEditor.onChange(editorState);
    }
  }

  insertText(text) {
    const contentEditor = this.editor.current.getContentEditor();
    let editorState = contentEditor.editorState;
    editorState = EditorUtils.Text.insertTextAtLine(editorState, 1, `\n${text}\n`);
    contentEditor.onChange(editorState);
  }

  handlePost = async evt => {
    evt && evt.preventDefault();

    const editor = this.editor.current;
    const { style, code, content } = editor.export();

    // TODO(mime): gotta be a simpler way then all this conversion.
    const title = EditorUtils.Text.getTextForLine(EditorState.createWithContent(convertFromRaw(content)), 0);

    const username = this.props.username;
    const name = title.replace(/[^A-Za-z0-9-]/g, '-');
    const { section, album } = JSON.parse(this.state.sectionAndAlbum);

    const thumb = this.state.fileInfo?.thumb || '';

    const variables = {
      username,
      section,
      album,
      name,
      title,
      hidden: false, // TODO(mime)
      thumb,
      style,
      code,
      content: JSON.stringify(content),
    };

    try {
      await this.props.mutate({
        variables,
        optimisticResponse: {
          __typename: 'Mutation',
          postContent: Object.assign({}, variables, { __typename: 'Content' }),
        },
      });
    } catch (ex) {
      this.setState({ errorMessage: messages.error }, () => {
        this.setState({ errorMessage: null });
      });
      return;
    }

    editor.clear();

    this.setState({ fileInfo: null, successMessage: messages.posted }, () => {
      this.setState({ successMessage: null });
    });
  };

  handleSectionAndAlbumChange = evt => {
    this.setState({ [evt.target.name]: evt.target.value });
    Cookies.set('sectionAndAlbum', evt.target.value);
  };

  generateSiteMapItem(item, albums, sectionName) {
    const value = {
      section: sectionName || item.name,
      album: sectionName ? item.name : '',
    };
    return (
      <MenuItem key={item.name} value={JSON.stringify(value)}>
        {!albums ? <>   </> : null}
        {item.title}
      </MenuItem>
    );
  }

  generateSiteMap(siteMap) {
    let items = [];
    for (let i = 0; i < siteMap.length; ++i) {
      const item = siteMap[i];
      const nextItem = siteMap[i + 1];

      let albums = [];
      if (nextItem?.album === 'main') {
        for (i += 1; i < siteMap.length; ++i) {
          const albumItem = siteMap[i];
          if (albumItem.album === 'main') {
            albums.push(this.generateSiteMapItem(albumItem, undefined, item.name));
          } else {
            i -= 1;
            break;
          }
        }
      }

      items.push(this.generateSiteMapItem(item, albums));
      items = items.concat(albums);
    }

    return items;
  }

  handleMediaAdd = fileInfos => {
    // We currently just grab the first one and disregard the other ones for purposes of getting the thumb.
    this.setState({ fileInfo: fileInfos[0] });
  };

  render() {
    return (

        <Toolbar>
          <form autoComplete="off" className={styles.editorForm}>
            <FormControl className={styles.editorToolbar}>
              <Select
                value={this.state.sectionAndAlbum}
                onChange={this.handleSectionAndAlbumChange}
                inputProps={{
                  name: 'sectionAndAlbum',
                }}
              >
                {this.generateSiteMap(this.props.data.fetchSiteMap)}
              </Select>
              <Button
                type="submit"
                onClick={this.handlePost}
                className={classNames('hw-button', 'hw-edit', styles.post)}
              >
                <F msg="post" />
              </Button>
            </FormControl>
          </form>
        </Toolbar>
        <ContentEditor
          ref={this.editor}
          showPlaceholder={true}
          mentions={this.props.data.fetchFollowing}
          content={null}
          onMediaAdd={this.handleMediaAdd}
        />
        <HiddenSnackbarShim
          message={this.state.successMessage || this.state.errorMessage}
          variant={this.state.successMessage ? 'success' : 'error'}
        />

    );
  }
}

export default compose(
  graphql(
    gql`
      query SiteMapAndUserQuery($username: String!) {
        fetchSiteMap(username: $username) {
          album
          hidden
          name
          section
          title
          username
        }

        fetchFollowing {
          name
          username
          profile_url
          avatar
          favicon
        }
      }
    `,
    {
      options: ({ username }) => ({
        variables: {
          username: username,
        },
      }),
      withRef: true,
    }
  ),
  graphql(
    gql`
      mutation postContent(
        $section: String!
        $album: String!
        $name: String!
        $title: String!
        $hidden: Boolean!
        $thumb: String!
        $style: String!
        $code: String!
        $content: String!
      ) {
        postContent(
          section: $section
          album: $album
          name: $name
          title: $title
          hidden: $hidden
          thumb: $thumb
          style: $style
          code: $code
          content: $content
        ) {
          username
          section
          album
          name
          title
          hidden
          thumb
          style
          code
          content
        }
      }
    `,
    { withRef: true }
  )
)(DashboardEditor);
<|endoftext|>


<commit_before>Template.editable.onCreated(function() {
	// When not editing individual fields, mark all fields as changed
	// from the start
	var startChanged = !this.data.showControls;
	this.changed = new ReactiveVar(startChanged);
	this.editingVersion = false;
});

Template.editable.onRendered(function() {
	var self = this;
	var editable = this.$('.editable');
	var options = {
		placeholder: {
			hideOnClick: false,
			text: self.data.placeholderText
		},
		anchor: {
			linkValidation: true,
			placeholderText: mf('editable.link.placeholder', "Paste link here...")
		},
		autoLink: true,
	};
	if (this.data.simple) {
		options.disableReturn = true;
		options.toolbar = false;
	}

	// UGLY The following two methods can be used to access and change
	// template state from other templates. This is a lot of indirection and
	// shows that I didn't think things through.

	// This method yields the current content if it was edited
	this.data.editedContent = function() {
		if (!self.changed.get()) return false;
		return self.data.simple ? editable.text() : editable.html();
	};

	// This method can be used to leave editing mode
	this.data.end = function() {
		self.changed.set(false);
	};

	// When the text changes while we are editing, the changes will be
	// inserted as new nodes instead of replacing the other nodes.
	// That's because Blaze doesn't know how to merge the current DOM
	// with the new text value it's given.
	// Merging the two versions is nontrivial and the current behaviour
	// is to just discard the local edits.
	self.autorun(function() {
		var currentData = Template.currentData();
		var currentText = currentData.text;

		// Here we instill the version we got in the DB
		// Most of the time, it will be the same as what
		// is already displayed
		currentText = currentText || '';
		if (currentText !== self.editingVersion) editable.html(currentText);

		if (self.editingVersion !== false && currentText !== self.editingVersion) {
			// Uh oh, not handling this well
			addMessage(mf('editable.sorrychanged', "Sorry, somebody else just changed that. Your changes have been discarded."), 'danger');

			//:-( REALLY BAD
			self.changed.set(false);
			self.editingVersion = false;
		}
	});

	self.editor = new MediumEditor(editable, options);
	editable.on('input', function() {
		if (!self.changed.get()) {
			self.changed.set(true);
			self.editingVersion = self.data.text;
		}
	});
});

Template.editable.helpers({
	showControls: function() {
		var instance = Template.instance();
		return instance.data.showControls && instance.changed.get();
	},

	wrapAttrs: function() {
		var instance = Template.instance();
		var classes = [];
		classes.push(instance.data.simple ? 'editable-wrap-simple' : 'editable-wrap-rich');
		return classes.join(' ');
	},

	editableAttrs: function() {
		var instance = Template.instance();
		var classes = [];
		if (instance.changed.get()) classes.push('editable-changed');
		return classes.join(' ');
	}
});

Template.editable.events({
	'click .js-editable-save': function(event, instance) {
		event.preventDefault();
		instance.changed.set(false);
		var editable = instance.$('.editable');
		var changedText = instance.data.simple ? editable.text() : editable.html();
		instance.data.store(changedText);
		instance.editingVersion = false;
	},

	'click .js-editable-cancel': function(event, instance) {
		event.preventDefault();
		instance.$('.editable').html(instance.data.text);
		instance.changed.set(false);
		instance.editingVersion = false;
	},

	'click .js-editable-edit': function(event, instance) {
		// Moving the cursor to the end of the editable element?
		// http://stackoverflow.com/questions/1125292/how-to-move-cursor-to-end-of-contenteditable-entity
		var selectEnd = function(el) {
			var range = document.createRange();
			range.selectNodeContents(el);
			range.collapse(false);
			var selection = window.getSelection();
			selection.removeAllRanges();
			selection.addRange(range);
		};
		selectEnd(instance.$('.editable')[0]);
	}
});
<commit_msg>and simplify editable attribute helpers<commit_after>Template.editable.onCreated(function() {
	// When not editing individual fields, mark all fields as changed
	// from the start
	var startChanged = !this.data.showControls;
	this.changed = new ReactiveVar(startChanged);
	this.editingVersion = false;
});

Template.editable.onRendered(function() {
	var self = this;
	var editable = this.$('.editable');
	var options = {
		placeholder: {
			hideOnClick: false,
			text: self.data.placeholderText
		},
		anchor: {
			linkValidation: true,
			placeholderText: mf('editable.link.placeholder', "Paste link here...")
		},
		autoLink: true,
	};
	if (this.data.simple) {
		options.disableReturn = true;
		options.toolbar = false;
	}

	// UGLY The following two methods can be used to access and change
	// template state from other templates. This is a lot of indirection and
	// shows that I didn't think things through.

	// This method yields the current content if it was edited
	this.data.editedContent = function() {
		if (!self.changed.get()) return false;
		return self.data.simple ? editable.text() : editable.html();
	};

	// This method can be used to leave editing mode
	this.data.end = function() {
		self.changed.set(false);
	};

	// When the text changes while we are editing, the changes will be
	// inserted as new nodes instead of replacing the other nodes.
	// That's because Blaze doesn't know how to merge the current DOM
	// with the new text value it's given.
	// Merging the two versions is nontrivial and the current behaviour
	// is to just discard the local edits.
	self.autorun(function() {
		var currentData = Template.currentData();
		var currentText = currentData.text;

		// Here we instill the version we got in the DB
		// Most of the time, it will be the same as what
		// is already displayed
		currentText = currentText || '';
		if (currentText !== self.editingVersion) editable.html(currentText);

		if (self.editingVersion !== false && currentText !== self.editingVersion) {
			// Uh oh, not handling this well
			addMessage(mf('editable.sorrychanged', "Sorry, somebody else just changed that. Your changes have been discarded."), 'danger');

			//:-( REALLY BAD
			self.changed.set(false);
			self.editingVersion = false;
		}
	});

	self.editor = new MediumEditor(editable, options);
	editable.on('input', function() {
		if (!self.changed.get()) {
			self.changed.set(true);
			self.editingVersion = self.data.text;
		}
	});
});

Template.editable.helpers({
	showControls: function() {
		var instance = Template.instance();
		return instance.data.showControls && instance.changed.get();
	},

	wrapAttrs: function() {
		var instance = Template.instance();
		return instance.data.simple ? 'editable-wrap-simple' : 'editable-wrap-rich';
	},

	editableAttrs: function() {
		var instance = Template.instance();
		return instance.changed.get() ? 'editable-changed' : '';
	}
});

Template.editable.events({
	'click .js-editable-save': function(event, instance) {
		event.preventDefault();
		instance.changed.set(false);
		var editable = instance.$('.editable');
		var changedText = instance.data.simple ? editable.text() : editable.html();
		instance.data.store(changedText);
		instance.editingVersion = false;
	},

	'click .js-editable-cancel': function(event, instance) {
		event.preventDefault();
		instance.$('.editable').html(instance.data.text);
		instance.changed.set(false);
		instance.editingVersion = false;
	},

	'click .js-editable-edit': function(event, instance) {
		// Moving the cursor to the end of the editable element?
		// http://stackoverflow.com/questions/1125292/how-to-move-cursor-to-end-of-contenteditable-entity
		var selectEnd = function(el) {
			var range = document.createRange();
			range.selectNodeContents(el);
			range.collapse(false);
			var selection = window.getSelection();
			selection.removeAllRanges();
			selection.addRange(range);
		};
		selectEnd(instance.$('.editable')[0]);
	}
});
<|endoftext|>


<commit_before>// Event store. Caching and automatically updating.
//
// Init:
//   var events = require('./events');
//
// Call:
//   events.getRecent(n, function (err, nEvs) { ... });
//
// Listen if events change:
//   events.on('events_changed', function () {
//     events.getRecent(n, function (err, nEvs) {
//       ...
//     });
//   });

var emitter = require('component-emitter');

var socket = require('../connection/socket');
var account = require('./account');

emitter(exports);

// The cache includes s most recent events in time order, most recent first.
// Cache builds up in two directions:
// - new events via socket grow head
// - user wants to see the older ones -> tail grows
var _cache = [];

// Listening for new events.
socket.on('tresdb_event', function (ev) {
  _cache.unshift(ev);
  exports.emit('events_changed');
});

var fetch = function (n, beforeTime, callback) {
  // Parameters:
  //   n
  //     max number of events to fetch
  //   beforeTime
  //     fetch only events before this time. Null = no time limit.
  //   callback
  //     function (err, arrayEvents)
  //
  $.ajax({
    url: '/api/events',
    method: 'GET',
    data: {
      n: n,
      beforeTime: beforeTime,  // can be null, meaning "before now"
    },
    dataType: 'json',
    headers: { 'Authorization': 'Bearer ' + account.getToken() },
    success: function (data) {
      // Parameters:
      //   data
      //     array of events, most recent first
      return callback(null, data);
    },
    error: function (jqxhr, statusCode, statusMessage) {
      return callback(new Error(statusMessage));
    },
  });
};

exports.getRecent = function (n, callback) {
  // Parameters:
  //   n
  //     integer
  //   callback
  //     function (err, events)
  //       Parameters:
  //         err
  //         events
  //           array, most recent event first

  var last;

  var s = _cache.length;
  var beforeTime = null;

  // Init number of items to fetch.
  var q = 0;

  // Check cache if n most recent already exists there.
  if (s > 0) {
    if (s >= n) {
      return callback(null, _cache.slice(0, n));
    }
    // Assert: cache is not empty but does not include enough items.
    // We must request n - s items, where s is the cache size.
    // The items must be from the time before the last cached item.
    q = n - s;
    last = _cache[s - 1];
    beforeTime = last.time;
  } else {
    q = n;
  }

  // Cache does not contain enough items so we fetch them.
  fetch(q, beforeTime, function (err, items) {
    if (err) {
      return callback(err);
    }

    // Update cache. Append to array.
    // Assert: the resulting array is in correct time order, oldest last.
    _cache = _cache.concat(items);

    // Inform views or other listeners that events have been changed.
    if (items.length > 0) {
      exports.emit('events_changed');
    }

    // Now cache should contain enough items.
    // Otherwise there is not enough existing items.
    if (_cache.length < n) {
      // Return all items there is.
      return callback(null, _cache);
    }
    // Return N most recent items.
    return callback(null, _cache.slice(0, n));
  });
};
<commit_msg>Remove unnecessarily complex event caching<commit_after>// Event store. Caching and automatically updating.
//
// Init:
//   var events = require('./events');
//
// Call:
//   events.getRecent(n, function (err, nEvs) { ... });
//
// Listen if events change:
//   events.on('events_changed', function () {
//     events.getRecent(n, function (err, nEvs) {
//       ...
//     });
//   });

var emitter = require('component-emitter');

var socket = require('../connection/socket');
var account = require('./account');

emitter(exports);

// Listen for new events and inform views that
// new events are available.
socket.on('tresdb_event', function (ev) {
  exports.emit('events_changed');
});

var fetch = function (n, beforeTime, callback) {
  // Parameters:
  //   n
  //     max number of events to fetch
  //   beforeTime
  //     fetch only events before this time. Null = no time limit.
  //   callback
  //     function (err, arrayEvents)
  //
  $.ajax({
    url: '/api/events',
    method: 'GET',
    data: {
      n: n,
      beforeTime: beforeTime,  // can be null, meaning "before now"
    },
    dataType: 'json',
    headers: { 'Authorization': 'Bearer ' + account.getToken() },
    success: function (data) {
      // Parameters:
      //   data
      //     array of events, most recent first
      return callback(null, data);
    },
    error: function (jqxhr, statusCode, statusMessage) {
      return callback(new Error(statusMessage));
    },
  });
};

exports.getRecent = function (n, callback) {
  // Parameters:
  //   n
  //     integer
  //   callback
  //     function (err, events)
  //       Parameters:
  //         err
  //         events
  //           array, most recent event first

  var beforeTime = null; // null equals now

  fetch(n, beforeTime, function (err, items) {
    if (err) {
      return callback(err);
    }
    return callback(null, items);
  });
};
<|endoftext|>


<commit_before>const loading = (state = false, action) => {
  switch (action.type) {
    case 'SIGN_IN': return true;
    case 'SIGN_IN_SUCCESS': return false;
    case 'SIGN_IN_ERROR': return false;

    case 'SIGN_UP': return true;
    case 'SIGN_UP_SUCCESS': return false;
    case 'SIGN_UP_ERROR': return false;

    case 'POST_MESSAGE': return true;
    case 'POST_MESSAGE_ERROR': return false;
    case 'POST_MESSAGE_SUCCESS': return false;

    case 'ADD_USER': return true;
    case 'ADD_USER_ERROR': return false;
    case 'ADD_USER_SUCCESS': return false;

    case 'DELETE_GROUP': return true;
    case 'ADD_GROUP_ERROR': return false;
    case 'ADD_GROUP_SUCCESS': return false;

    case 'CREATE_GROUP': return true;
    case 'CREATE_GROUP_ERROR': return false;
    case 'CREATE_GROUP_SUCCESS': return false;

    case 'GET_MESSAGES': return true;
    case 'GET_MESSAGES_ERROR': return false;
    case 'GET_MESSAGES_SUCCESS': return false;

    case 'GET_GROUP_MEMBERS': return true;
    case 'GET_GROUP_MEMBERS_ERROR': return true;
    case 'GET_GROUP_MEMBERS_SUCCESS': return true;

    case 'GET_POST_IT_MEMBERS': return true;
    case 'GET_POST_IT_MEMBERS_ERROR': return false;
    case 'GET_POST_IT_MEMBERS_SUCCESS': return false;

    case 'GET_ALL_GROUPS_FOR_A_USER': return true;
    case 'GET_ALL_GROUPS_FOR_A_USER_ERROR': return false;
    case 'GET_ALL_GROUPS_FOR_A_USER_SUCCESS': return false;

    case 'GET_ALL_GROUPS': return true;
    case 'GET_ALL_GROUPS_ERROR': return false;
    case 'GET_ALL_GROUPS_SUCCESS': return false;

    case 'DELETE_MEMBER': return true;
    case 'DELETE_MEMBER_ERROR': return false;
    case 'DELETE_MEMBER_SUCCESS': return false;

    default: return state;
  }
};

export default loading;
<commit_msg>add actions to handle loading groups<commit_after>const loading = (state = false, action) => {
  switch (action.type) {
    case 'SIGN_IN': return true;
    case 'SIGN_IN_SUCCESS': return false;
    case 'SIGN_IN_ERROR': return false;

    case 'SIGN_UP': return true;
    case 'SIGN_UP_SUCCESS': return false;
    case 'SIGN_UP_ERROR': return false;

    case 'POST_MESSAGE': return true;
    case 'POST_MESSAGE_ERROR': return false;
    case 'POST_MESSAGE_SUCCESS': return false;

    case 'ADD_USER': return true;
    case 'ADD_USER_ERROR': return false;
    case 'ADD_USER_SUCCESS': return false;

    case 'DELETE_GROUP': return true;
    case 'ADD_GROUP_ERROR': return false;
    case 'ADD_GROUP_SUCCESS': return false;

    case 'CREATE_GROUP': return true;
    case 'CREATE_GROUP_ERROR': return false;
    case 'CREATE_GROUP_SUCCESS': return false;

    case 'GET_MESSAGES': return true;
    case 'GET_MESSAGES_ERROR': return false;
    case 'GET_MESSAGES_SUCCESS': return false;

    case 'GET_GROUP_MEMBERS': return true;
    case 'GET_GROUP_MEMBERS_ERROR': return true;
    case 'GET_GROUP_MEMBERS_SUCCESS': return true;

    case 'GET_POST_IT_MEMBERS': return true;
    case 'GET_POST_IT_MEMBERS_ERROR': return false;
    case 'GET_POST_IT_MEMBERS_SUCCESS': return false;

    case 'GET_ALL_GROUPS_FOR_A_USER': return true;
    case 'GET_ALL_GROUPS_FOR_A_USER_ERROR': return false;
    case 'GET_ALL_GROUPS_FOR_A_USER_SUCCESS': return false;

    case 'GET_ALL_GROUPS_FOR_A_USER_AT_ONCE': return true;
    case 'GET_ALL_GROUPS_FOR_A_USER_AT_ONCE_ERROR': return false;
    case 'GET_ALL_GROUPS_FOR_A_USER_AT_ONCE_SUCCESS': return false;

    case 'GET_ALL_GROUPS': return true;
    case 'GET_ALL_GROUPS_ERROR': return false;
    case 'GET_ALL_GROUPS_SUCCESS': return false;

    case 'DELETE_MEMBER': return true;
    case 'DELETE_MEMBER_ERROR': return false;
    case 'DELETE_MEMBER_SUCCESS': return false;

    default: return state;
  }
};

export default loading;
<|endoftext|>


<commit_before>import axios from 'axios';

const ArticlesApi = {

  fetchAll() {
    const url = `${process.env.API_URL}articles`;
    const options = { headers: { 'Content-Type': 'application/json' } };

    return axios.get(url, options)
      .then(response => Promise.resolve(response.data))
      .catch(error => Promise.reject(error));
  },
};

export default ArticlesApi;
<commit_msg>review(REC-2) : reviews Adrien<commit_after>import axios from 'axios';

const ArticlesApi = {

  fetchAll() {
    const url = `${process.env.API_URL}articles`;
    const options = { headers: { 'Content-Type': 'application/json' } };

    return axios.get(url, options)
      .then(response => response.data)
      .catch(error => Promise.reject(error));
  },
};

export default ArticlesApi;
<|endoftext|>


<commit_before>export const ADMIN = 'ADMIN';
export const ANY = 'ANY';
export const HOME = 'Home';
export const SQUAD_LEADER = 'SQUAD_LEADER';

export const canUserSeeLink = (link, user = {}) =>
  link.show &&
  (
    link.roles.includes(ANY) ||
    (user.squadLeader && link.roles.some((role) => role === SQUAD_LEADER)) ||
    (user.admin && link.roles.some((role) => role === ADMIN))
  );

export const canUserAccessPattern = (user, pattern) => {
  if (typeof user === 'undefined' || user === null) {
    return false;
  }

  if (pattern === '/') {
    return true;
  }

  const patternMainRouteKey = Object.keys(mainRoutes).find((key) => mainRoutes[key].links.some(({ path }) => path === pattern));

  if (!patternMainRouteKey) {
    return false;
  }

  const link = mainRoutes[patternMainRouteKey].links.find(({ path }) => path === pattern);
  const { roles } = link;

  return roles.includes(ANY) || (user.squadLeader && roles.includes(SQUAD_LEADER)) || (user.admin && roles.includes(ADMIN));
};

export const getVisibleMainRoutesForUser = (user) => {
  if (typeof user === 'undefined' || user === null) {
    return [];
  }

  return Object.keys(mainRoutes).filter((key) => mainRoutes[key].links.some((link) => canUserSeeLink(link, user)));
};

export const mainRoutes = {
  challenges: {
    displayName: 'Challenges',
    links: [
      {
        name: 'Previous',
        path: '/challenges',
        roles: [ANY],
        show: true
      },
      {
        name: 'Make A Challenge',
        path: '/challenges/new',
        roles: [ANY],
        show: true
      },
      {
        name: 'Evaluate',
        path: '/challenges/evaluate',
        roles: [ADMIN, SQUAD_LEADER],
        show: true
      }
    ]
  },
  results: {
    displayName: 'Results',
    links: [
      {
        name: 'Approve',
        path: '/results/pending',
        roles: [ADMIN],
        show: true
      },
      {
        name: 'Completed',
        path: '/results/completed',
        roles: [ANY],
        show: true
      }
    ]
  },
  performances: {
    displayName: 'Performances',
    links: [
      {
        name: 'All',
        path: '/performances',
        roles: [ADMIN],
        show: true
      },
      {
        name: 'Create',
        path: '/performances/new',
        roles: [ADMIN],
        show: true
      }
    ]
  },
  users: {
    displayName: 'Users',
    links: [
      {
        path: '/users/:nameNumber',
        roles: [ADMIN],
        show: false
      },
      {
        name: 'Search',
        path: '/search',
        roles: [ADMIN],
        show: true
      }
    ]
  }
};
<commit_msg>client: remove results->completed route for normal users<commit_after>export const ADMIN = 'ADMIN';
export const ANY = 'ANY';
export const HOME = 'Home';
export const SQUAD_LEADER = 'SQUAD_LEADER';

export const canUserSeeLink = (link, user = {}) =>
  link.show &&
  (
    link.roles.includes(ANY) ||
    (user.squadLeader && link.roles.some((role) => role === SQUAD_LEADER)) ||
    (user.admin && link.roles.some((role) => role === ADMIN))
  );

export const canUserAccessPattern = (user, pattern) => {
  if (typeof user === 'undefined' || user === null) {
    return false;
  }

  if (pattern === '/') {
    return true;
  }

  const patternMainRouteKey = Object.keys(mainRoutes).find((key) => mainRoutes[key].links.some(({ path }) => path === pattern));

  if (!patternMainRouteKey) {
    return false;
  }

  const link = mainRoutes[patternMainRouteKey].links.find(({ path }) => path === pattern);
  const { roles } = link;

  return roles.includes(ANY) || (user.squadLeader && roles.includes(SQUAD_LEADER)) || (user.admin && roles.includes(ADMIN));
};

export const getVisibleMainRoutesForUser = (user) => {
  if (typeof user === 'undefined' || user === null) {
    return [];
  }

  return Object.keys(mainRoutes).filter((key) => mainRoutes[key].links.some((link) => canUserSeeLink(link, user)));
};

export const mainRoutes = {
  challenges: {
    displayName: 'Challenges',
    links: [
      {
        name: 'Previous',
        path: '/challenges',
        roles: [ANY],
        show: true
      },
      {
        name: 'Make A Challenge',
        path: '/challenges/new',
        roles: [ANY],
        show: true
      },
      {
        name: 'Evaluate',
        path: '/challenges/evaluate',
        roles: [ADMIN, SQUAD_LEADER],
        show: true
      }
    ]
  },
  results: {
    displayName: 'Results',
    links: [
      {
        name: 'Approve',
        path: '/results/pending',
        roles: [ADMIN],
        show: true
      },
      {
        name: 'Completed',
        path: '/results/completed',
        roles: [ADMIN, SQUAD_LEADER],
        show: true
      }
    ]
  },
  performances: {
    displayName: 'Performances',
    links: [
      {
        name: 'All',
        path: '/performances',
        roles: [ADMIN],
        show: true
      },
      {
        name: 'Create',
        path: '/performances/new',
        roles: [ADMIN],
        show: true
      }
    ]
  },
  users: {
    displayName: 'Users',
    links: [
      {
        path: '/users/:nameNumber',
        roles: [ADMIN],
        show: false
      },
      {
        name: 'Search',
        path: '/search',
        roles: [ADMIN],
        show: true
      }
    ]
  }
};
<|endoftext|>


<commit_before>import u from 'updeep';

import { assert, assertHas, localeComparator, NONE_CATEGORY_ID, translate as $t } from '../helpers';

import { Category } from '../models';

import * as backend from './backend';

import {
    compose,
    createReducerFromMap,
    fillOutcomeHandlers,
    updateMapIf,
    SUCCESS
} from './helpers';

import { CREATE_CATEGORY, UPDATE_CATEGORY, DELETE_CATEGORY } from './actions';

// Helpers
function sortCategories(items) {
    let copy = items.slice();
    copy.sort((a, b) => localeComparator(a.title, b.title));
    return copy;
}

// Basic actions creators
const basic = {
    createCategory(category) {
        return {
            type: CREATE_CATEGORY,
            category
        };
    },

    updateCategory(former, category) {
        return {
            type: UPDATE_CATEGORY,
            id: former.id,
            category
        };
    },

    deleteCategory(category, replace) {
        return {
            type: DELETE_CATEGORY,
            id: category.id,
            replaceByCategoryId: replace
        };
    }
};

const fail = {},
    success = {};
fillOutcomeHandlers(basic, fail, success);

export function create(category) {
    assertHas(category, 'title', 'CreateCategory expects an object that has a title field');
    assertHas(category, 'color', 'CreateCategory expects an object that has a color field');

    return dispatch => {
        dispatch(basic.createCategory(category));
        backend
            .addCategory(category)
            .then(created => {
                dispatch(success.createCategory(created));
            })
            .catch(err => {
                dispatch(fail.createCategory(err, category));
            });
    };
}

export function update(former, category) {
    assert(former instanceof Category, 'UpdateCategory first arg must be a Category');
    assertHas(category, 'title', 'UpdateCategory second arg must have a title field');
    assertHas(category, 'color', 'UpdateCategory second arg must have a color field');

    if (typeof category.threshold !== 'undefined') {
        assert(
            typeof category.threshold === 'number',
            'UpdateCategory second' + ' arg threshold field must be a number'
        );
    }

    return dispatch => {
        dispatch(basic.updateCategory(former, category));
        backend
            .updateCategory(former.id, category)
            .then(updated => {
                dispatch(success.updateCategory(former, updated));
            })
            .catch(err => {
                dispatch(fail.updateCategory(err, former, category));
            });
    };
}

export function destroy(category, replace) {
    assert(category instanceof Category, 'DeleteCategory first arg must be a Category');
    assert(typeof replace === 'string', 'DeleteCategory second arg must be a String id');

    // The server expects an empty string if there's no replacement category.
    let serverReplace = replace === NONE_CATEGORY_ID ? '' : replace;

    return dispatch => {
        dispatch(basic.deleteCategory(category, replace));
        backend
            .deleteCategory(category.id, serverReplace)
            .then(() => {
                dispatch(success.deleteCategory(category, replace));
            })
            .catch(err => {
                dispatch(fail.deleteCategory(err, category, replace));
            });
    };
}

// States
const categoryState = u(
    {
        // Maps id to categories.
        map: {},
        // The categories themselves.
        items: []
    },
    {}
);

// Reducers
function reduceCreate(state, action) {
    let { status } = action;

    if (status === SUCCESS) {
        let c = new Category(action.category);
        return u(
            {
                items: compose(items => [c].concat(items), sortCategories),
                map: { [c.id]: c }
            },
            state
        );
    }

    return state;
}

function reduceUpdate(state, action) {
    let { status } = action;

    if (status === SUCCESS) {
        let updated = action.category;
        return u(
            {
                items: compose(
                    updateMapIf('id', updated.id, c => new Category(u(updated, c))),
                    sortCategories
                ),
                map: { [updated.id]: updated }
            },
            state
        );
    }

    return state;
}

function reduceDelete(state, action) {
    let { status } = action;

    if (status === SUCCESS) {
        let id = action.id;
        return u(
            {
                items: u.reject(c => c.id === id),
                map: u.omit(id)
            },
            state
        );
    }

    return state;
}

const reducers = {
    CREATE_CATEGORY: reduceCreate,
    UPDATE_CATEGORY: reduceUpdate,
    DELETE_CATEGORY: reduceDelete
};

export const reducer = createReducerFromMap(categoryState, reducers);

// Initial state
export function initialState(categories) {
    const NONE_CATEGORY = new Category({
        id: NONE_CATEGORY_ID,
        title: $t('client.category.none'),
        color: '#000000'
    });

    let items = sortCategories([NONE_CATEGORY].concat(categories).map(c => new Category(c)));

    let map = {};
    for (let c of items) map[c.id] = c;

    return u(
        {
            items,
            map
        },
        {}
    );
}

// Getters
export function all(state) {
    return state.items;
}

export function allButNone(state) {
    return all(state).filter(c => c.id !== NONE_CATEGORY_ID);
}

export function fromId(state, id) {
    let map = state.map;
    assert(typeof map[id] !== 'undefined', `fromId lookup failed for id: ${id}`);
    return map[id];
}
<commit_msg>Address supplementary style fix uncovered by prettier use;<commit_after>import u from 'updeep';

import { assert, assertHas, localeComparator, NONE_CATEGORY_ID, translate as $t } from '../helpers';

import { Category } from '../models';

import * as backend from './backend';

import {
    compose,
    createReducerFromMap,
    fillOutcomeHandlers,
    updateMapIf,
    SUCCESS
} from './helpers';

import { CREATE_CATEGORY, UPDATE_CATEGORY, DELETE_CATEGORY } from './actions';

// Helpers
function sortCategories(items) {
    let copy = items.slice();
    copy.sort((a, b) => localeComparator(a.title, b.title));
    return copy;
}

// Basic actions creators
const basic = {
    createCategory(category) {
        return {
            type: CREATE_CATEGORY,
            category
        };
    },

    updateCategory(former, category) {
        return {
            type: UPDATE_CATEGORY,
            id: former.id,
            category
        };
    },

    deleteCategory(category, replace) {
        return {
            type: DELETE_CATEGORY,
            id: category.id,
            replaceByCategoryId: replace
        };
    }
};

const fail = {},
    success = {};
fillOutcomeHandlers(basic, fail, success);

export function create(category) {
    assertHas(category, 'title', 'CreateCategory expects an object that has a title field');
    assertHas(category, 'color', 'CreateCategory expects an object that has a color field');

    return dispatch => {
        dispatch(basic.createCategory(category));
        backend
            .addCategory(category)
            .then(created => {
                dispatch(success.createCategory(created));
            })
            .catch(err => {
                dispatch(fail.createCategory(err, category));
            });
    };
}

export function update(former, category) {
    assert(former instanceof Category, 'UpdateCategory first arg must be a Category');
    assertHas(category, 'title', 'UpdateCategory second arg must have a title field');
    assertHas(category, 'color', 'UpdateCategory second arg must have a color field');

    if (typeof category.threshold !== 'undefined') {
        assert(
            typeof category.threshold === 'number',
            'UpdateCategory second arg threshold field must be a number'
        );
    }

    return dispatch => {
        dispatch(basic.updateCategory(former, category));
        backend
            .updateCategory(former.id, category)
            .then(updated => {
                dispatch(success.updateCategory(former, updated));
            })
            .catch(err => {
                dispatch(fail.updateCategory(err, former, category));
            });
    };
}

export function destroy(category, replace) {
    assert(category instanceof Category, 'DeleteCategory first arg must be a Category');
    assert(typeof replace === 'string', 'DeleteCategory second arg must be a String id');

    // The server expects an empty string if there's no replacement category.
    let serverReplace = replace === NONE_CATEGORY_ID ? '' : replace;

    return dispatch => {
        dispatch(basic.deleteCategory(category, replace));
        backend
            .deleteCategory(category.id, serverReplace)
            .then(() => {
                dispatch(success.deleteCategory(category, replace));
            })
            .catch(err => {
                dispatch(fail.deleteCategory(err, category, replace));
            });
    };
}

// States
const categoryState = u(
    {
        // Maps id to categories.
        map: {},
        // The categories themselves.
        items: []
    },
    {}
);

// Reducers
function reduceCreate(state, action) {
    let { status } = action;

    if (status === SUCCESS) {
        let c = new Category(action.category);
        return u(
            {
                items: compose(items => [c].concat(items), sortCategories),
                map: { [c.id]: c }
            },
            state
        );
    }

    return state;
}

function reduceUpdate(state, action) {
    let { status } = action;

    if (status === SUCCESS) {
        let updated = action.category;
        return u(
            {
                items: compose(
                    updateMapIf('id', updated.id, c => new Category(u(updated, c))),
                    sortCategories
                ),
                map: { [updated.id]: updated }
            },
            state
        );
    }

    return state;
}

function reduceDelete(state, action) {
    let { status } = action;

    if (status === SUCCESS) {
        let id = action.id;
        return u(
            {
                items: u.reject(c => c.id === id),
                map: u.omit(id)
            },
            state
        );
    }

    return state;
}

const reducers = {
    CREATE_CATEGORY: reduceCreate,
    UPDATE_CATEGORY: reduceUpdate,
    DELETE_CATEGORY: reduceDelete
};

export const reducer = createReducerFromMap(categoryState, reducers);

// Initial state
export function initialState(categories) {
    const NONE_CATEGORY = new Category({
        id: NONE_CATEGORY_ID,
        title: $t('client.category.none'),
        color: '#000000'
    });

    let items = sortCategories([NONE_CATEGORY].concat(categories).map(c => new Category(c)));

    let map = {};
    for (let c of items) map[c.id] = c;

    return u(
        {
            items,
            map
        },
        {}
    );
}

// Getters
export function all(state) {
    return state.items;
}

export function allButNone(state) {
    return all(state).filter(c => c.id !== NONE_CATEGORY_ID);
}

export function fromId(state, id) {
    let map = state.map;
    assert(typeof map[id] !== 'undefined', `fromId lookup failed for id: ${id}`);
    return map[id];
}
<|endoftext|>


<commit_before>'use strict';
import { _ } from 'meteor/underscore';
import { $ } from 'meteor/jquery';
import { FlowRouter } from 'meteor/kadira:flow-router';
import { Template } from 'meteor/templating';
import { dbVariables } from '../../db/dbVariables';

Template.pagination.helpers({
  haveData() {
    const totalCount = dbVariables.get(this.useVariableForTotalCount);
    const totalPages = Math.ceil(totalCount / this.dataNumberPerPage);

    return totalPages > 0;
  },
  pages() {
    const totalCount = dbVariables.get(this.useVariableForTotalCount);
    const totalPages = Math.ceil(totalCount / this.dataNumberPerPage);
    if (totalPages <= 7) {
      return _.range(1, totalPages + 1);
    }
    else {
      const offset = this.offset.get();
      const currentPage = (offset / this.dataNumberPerPage) + 1;
      if (currentPage - 3 >= 1 && currentPage + 3 <= totalPages) {
        return _.range(currentPage - 3, currentPage + 4);
      }
      else if (currentPage - 3 < 1) {
        return _.range(1, 8);
      }
      else if (currentPage + 3 > totalPages) {
        return _.range(totalPages - 6, totalPages + 1);
      }
    }
  },
  currentPage() {
    const offset = this.offset.get();

    return (offset / this.dataNumberPerPage) + 1;
  },
  totalPages() {
    const totalCount = dbVariables.get(this.useVariableForTotalCount);
    const totalPages = Math.ceil(totalCount / this.dataNumberPerPage);

    return totalPages;
  },
  pageItemClass(page) {
    const offset = this.offset.get();
    const currentPage = (offset / this.dataNumberPerPage) + 1;

    return (page === currentPage) ? 'page-item active' : 'page-item';
  },
  pageLinkHref(page) {
    const templateInstance = Template.instance();
    const data = templateInstance.data;
    const totalCount = dbVariables.get(data.useVariableForTotalCount);
    const totalPages = Math.ceil(totalCount / data.dataNumberPerPage);

    if (page === 'end') {
      return './' + totalPages;
    }

    const offset = this.offset.get();
    const currentPage = (offset / this.dataNumberPerPage) + 1;
    if (page === 'prev') {
      return './' + Math.max(1, currentPage - 1);
    }
    if (page === 'next') {
      return './' + Math.min(totalPages, currentPage + 1);
    }

    return './' + page;
  }
});
Template.pagination.events({
  'click a[href]'(event, templateInstance) {
    const data = templateInstance.data;
    if (! data.useHrefRoute) {
      event.preventDefault();
      const href = $(event.currentTarget).attr('href');
      const toPage = parseInt(href.slice(2), 10);
      const newOffset = (toPage - 1) * data.dataNumberPerPage;
      data.offset.set(newOffset);
    }
  },
  'submit form'(event, templateInstance) {
    event.preventDefault();

    const data = templateInstance.data;
    const targetPage = Number(templateInstance.$('form')
      .find('input[name=page]')
      .val());

    if (data.useHrefRoute) {
      FlowRouter.go(FlowRouter.path(FlowRouter.getRouteName(), { page: targetPage }));
    }
    else {
      const newOffset = (targetPage - 1) * data.dataNumberPerPage;
      data.offset.set(newOffset);
    }
  },
  'click form button'(event) {
    event.stopPropagation(); // 防止與外層的 click button 事件衝突 (e.g., 帳號資訊 > 玩家紀錄 > 金管會相關紀錄按鍵)
  }
});
<commit_msg>Update pagination.js<commit_after>'use strict';
import { _ } from 'meteor/underscore';
import { $ } from 'meteor/jquery';
import { FlowRouter } from 'meteor/kadira:flow-router';
import { Template } from 'meteor/templating';
import { dbVariables } from '../../db/dbVariables';

Template.pagination.helpers({
  haveData() {
    const totalCount = dbVariables.get(this.useVariableForTotalCount);

    return totalCount > 0;
  },
  pages() {
    const totalCount = dbVariables.get(this.useVariableForTotalCount);
    const totalPages = Math.ceil(totalCount / this.dataNumberPerPage);
    if (totalPages <= 7) {
      return _.range(1, totalPages + 1);
    }
    else {
      const offset = this.offset.get();
      const currentPage = (offset / this.dataNumberPerPage) + 1;
      if (currentPage - 3 >= 1 && currentPage + 3 <= totalPages) {
        return _.range(currentPage - 3, currentPage + 4);
      }
      else if (currentPage - 3 < 1) {
        return _.range(1, 8);
      }
      else if (currentPage + 3 > totalPages) {
        return _.range(totalPages - 6, totalPages + 1);
      }
    }
  },
  currentPage() {
    const offset = this.offset.get();

    return (offset / this.dataNumberPerPage) + 1;
  },
  totalPages() {
    const totalCount = dbVariables.get(this.useVariableForTotalCount);
    const totalPages = Math.ceil(totalCount / this.dataNumberPerPage);

    return totalPages;
  },
  pageItemClass(page) {
    const offset = this.offset.get();
    const currentPage = (offset / this.dataNumberPerPage) + 1;

    return (page === currentPage) ? 'page-item active' : 'page-item';
  },
  pageLinkHref(page) {
    const templateInstance = Template.instance();
    const data = templateInstance.data;
    const totalCount = dbVariables.get(data.useVariableForTotalCount);
    const totalPages = Math.ceil(totalCount / data.dataNumberPerPage);

    if (page === 'end') {
      return './' + totalPages;
    }

    const offset = this.offset.get();
    const currentPage = (offset / this.dataNumberPerPage) + 1;
    if (page === 'prev') {
      return './' + Math.max(1, currentPage - 1);
    }
    if (page === 'next') {
      return './' + Math.min(totalPages, currentPage + 1);
    }

    return './' + page;
  }
});
Template.pagination.events({
  'click a[href]'(event, templateInstance) {
    const data = templateInstance.data;
    if (! data.useHrefRoute) {
      event.preventDefault();
      const href = $(event.currentTarget).attr('href');
      const toPage = parseInt(href.slice(2), 10);
      const newOffset = (toPage - 1) * data.dataNumberPerPage;
      data.offset.set(newOffset);
    }
  },
  'submit form'(event, templateInstance) {
    event.preventDefault();

    const data = templateInstance.data;
    const targetPage = Number(templateInstance.$('form')
      .find('input[name=page]')
      .val());

    if (data.useHrefRoute) {
      FlowRouter.go(FlowRouter.path(FlowRouter.getRouteName(), { page: targetPage }));
    }
    else {
      const newOffset = (targetPage - 1) * data.dataNumberPerPage;
      data.offset.set(newOffset);
    }
  },
  'click form button'(event) {
    event.stopPropagation(); // 防止與外層的 click button 事件衝突 (e.g., 帳號資訊 > 玩家紀錄 > 金管會相關紀錄按鍵)
  }
});
<|endoftext|>


<commit_before>Template.pagePage.rendered = function() {
    /**
     * Animate objects once the page is rendered
     */
    $('.container-map').addClass('animated fadeIn');
    if ($('.body-copy.rare-climate-zone').length) {
        $('.body-copy').addClass('animated fadeInUp');
        //$('.body-text').wrapInner('

');
        //$('.body-text p.eng').replaceWith(function(){
            //return $("
", {html: $(this).html()}).addClass('riparian');
        //});

    }
    else {
        $('.body-copy').addClass('animated fadeInLeft');
    }
    $('.body-copy').css('-webkit-animation-duration', '400ms');
    $('.body-copy').css('animation-duration', '400ms');
    $('.subpage-button, h3').addClass('animated fadeInDown ');
    $('.subpage-button ').css('animation-duration', '300ms');
    $('.canyoneer-natural-walks').css('animation-duration', '400ms');
    $('h3').css('animation-duration', '500ms');
};

Template.pagePage.events({
    /**
     * Custom actions for page links
     */
    'click .subpage-button': function(e) {
        e.preventDefault();
        destination = this._id;

        /**
         * Animate the page before we leave. We're mostly removing
         * the layers on top of the map
         */
        var animateContentOut = function() {
            if ($('.subpage-selector').length) {
                $('.subpage-selector-header h3').addClass('animated fadeOutUp');
                $('.subpage-button-1').css('-webkit-animation-delay', '.15s');
                $('.subpage-button-2').css('-webkit-animation-delay', '.1s');
                $('.subpage-button-3').css('-webkit-animation-delay', '.05s');
                $('.subpage-button-4').css('-webkit-animation-delay', '.0s');
                $('.subpage-button').addClass('animated fadeOutUp');
                $('.subpage-selector-header h3').addClass('animated fadeOutUp');
                $('.btn-home').addClass('animated fadeOutDownCustom');
            }
            else {
                $('.body-copy').addClass('animated fadeOutLeft');
                $('.container-map').addClass('animated fadeOut');
            }
        }
        animateContentOut();

        window.setTimeout(function() {
            goDestination();
        }, 600);
        function goDestination() {
            Router.go('pagePage', {_id: destination});
        }
    },
    'click .btn-home': function(e) {

        e.preventDefault();
        destination = this.componentId;

        /**
         * Animate the page before we leave. We're mostly removing
         * the layers on top of the map
         */
        var animateContentOut = function() {
            // Animate all overlays
            $('.container-map').addClass('animated fadeOut');
            if ($('.body-copy.horizontal').length) {
                $('.body-copy').addClass('animated fadeOutDownCustom');
            }
            else if ($('.subpage-selector').length) {
                $('.subpage-selector-header h3').addClass('animated fadeOutUp');
                $('.subpage-button-1').css('-webkit-animation-delay', '.15s');
                $('.subpage-button-2').css('-webkit-animation-delay', '.1s');
                $('.subpage-button-3').css('-webkit-animation-delay', '.05s');
                $('.subpage-button-4').css('-webkit-animation-delay', '.0s');
                $('.subpage-button').addClass('animated fadeOutUp');
                $('.subpage-selector-header h3').addClass('animated fadeOutUp');
                $('.btn-home').addClass('animated fadeOutDownCustom');
            }
            else {
                $('.body-copy').addClass('animated fadeOutLeft');
            }
        }
        animateContentOut();

        window.setTimeout(function() {
            goDestination();
        }, 600);
        function goDestination() {
            Router.go('componentPage', {_id: destination});
        }
    }
});

Template.pagePage.helpers({
    doorf: function() {
        return "well of course";
    },
    subPages: function() {
        console.log("This _is", this._id);
        data = Pages.find({parentId: this._id}, {sort: {order: 1}});
        if (data.count()) {
            return data;
        }
        else {
            return false;
        }
    }
});
<commit_msg>Proper navigation for the home button<commit_after>Template.pagePage.rendered = function() {
    /**
     * Animate objects once the page is rendered
     */
    $('.container-map').addClass('animated fadeIn');
    if ($('.body-copy.rare-climate-zone').length) {
        $('.body-copy').addClass('animated fadeInUp');
        //$('.body-text').wrapInner('

');
        //$('.body-text p.eng').replaceWith(function(){
            //return $("
", {html: $(this).html()}).addClass('riparian');
        //});

    }
    else {
        $('.body-copy').addClass('animated fadeInLeft');
    }
    $('.body-copy').css('-webkit-animation-duration', '400ms');
    $('.body-copy').css('animation-duration', '400ms');
    $('.subpage-button, h3').addClass('animated fadeInDown ');
    $('.subpage-button ').css('animation-duration', '300ms');
    $('.canyoneer-natural-walks').css('animation-duration', '400ms');
    $('h3').css('animation-duration', '500ms');
};

Template.pagePage.events({
    /**
     * Custom actions for page links
     */
    'click .subpage-button': function(e) {
        e.preventDefault();
        destination = this._id;

        /**
         * Animate the page before we leave. We're mostly removing
         * the layers on top of the map
         */
        var animateContentOut = function() {
            if ($('.subpage-selector').length) {
                $('.subpage-selector-header h3').addClass('animated fadeOutUp');
                $('.subpage-button-1').css('-webkit-animation-delay', '.15s');
                $('.subpage-button-2').css('-webkit-animation-delay', '.1s');
                $('.subpage-button-3').css('-webkit-animation-delay', '.05s');
                $('.subpage-button-4').css('-webkit-animation-delay', '.0s');
                $('.subpage-button').addClass('animated fadeOutUp');
                $('.subpage-selector-header h3').addClass('animated fadeOutUp');
                $('.btn-home').addClass('animated fadeOutDownCustom');
            }
            else {
                $('.body-copy').addClass('animated fadeOutLeft');
                $('.container-map').addClass('animated fadeOut');
            }
        }
        animateContentOut();

        window.setTimeout(function() {
            goDestination();
        }, 600);
        function goDestination() {
            Router.go('subPagePage', {_id: destination});
        }
    },
    'click .btn-home': function(e) {

        e.preventDefault();
        destination = this.componentId;

        /**
         * Animate the page before we leave. We're mostly removing
         * the layers on top of the map
         */
        var animateContentOut = function() {
            // Animate all overlays
            $('.container-map').addClass('animated fadeOut');
            if ($('.body-copy.horizontal').length) {
                $('.body-copy').addClass('animated fadeOutDownCustom');
            }
            else if ($('.subpage-selector').length) {
                $('.subpage-selector-header h3').addClass('animated fadeOutUp');
                $('.subpage-button-1').css('-webkit-animation-delay', '.15s');
                $('.subpage-button-2').css('-webkit-animation-delay', '.1s');
                $('.subpage-button-3').css('-webkit-animation-delay', '.05s');
                $('.subpage-button-4').css('-webkit-animation-delay', '.0s');
                $('.subpage-button').addClass('animated fadeOutUp');
                $('.subpage-selector-header h3').addClass('animated fadeOutUp');
                $('.btn-home').addClass('animated fadeOutDownCustom');
            }
            else {
                $('.body-copy').addClass('animated fadeOutLeft');
            }
        }
        animateContentOut();

        window.setTimeout(function() {
            goDestination();
        }, 600);
        function goDestination() {
            Router.go('componentPage', {_id: destination});
        }
    }
});

Template.pagePage.helpers({
    doorf: function() {
        return "well of course";
    },
    subPages: function() {
        console.log("This _is", this._id);
        data = Pages.find({parentId: this._id}, {sort: {order: 1}});
        if (data.count()) {
            return data;
        }
        else {
            return false;
        }
    }
});
<|endoftext|>


<commit_before>angular.module('app.services', [])

.factory('Game', ['$q', '$http', 'store', 'socket', '$timeout', 'ionicToast', function($q, $http, store, socket, $timeout, ionicToast){

  var obj = {
    submitting: false,

    started: false,

    response: '',

    topic: '',

    isReader: false,

    isCreator: false,

    game: {

      id: undefined,

      max_score: undefined,

      guesser: undefined,

      //Array of objects, with id, name, guessed, and score
      players: [],

      //Object containing all pertinent info for the round
      current_round: {

        reader_name: undefined,

        reader_id: undefined,

        //All answers in, ready to start guessing
        ready: false,

        //Array of objects, with id, text, guessed, and user_id
        responses: [

        ],

        //Current round's topic
        topic: ''
      },

      //Array of rounds from the server
      rounds: []
    },

    // The guess a user is going to make
    guess: {
      user: undefined,
      response: undefined
    },

    checkGame: function () {
      var remote_id = store.get('remote_id');
      if (!remote_id) {
        return $q.resolve();
      }
      return $http({
        url: Config.api + '/users/' + remote_id,
        method: 'get'
      })
      .then(function (response) {
        var current_game_id = response.data.user.current_game_id;
        store.set('current_game_id', current_game_id);
        obj.game.id = current_game_id;
        return !!current_game_id;
      })
      .catch(function (error) {
        console.log('check game error', error);
      })
    },

    getGame: function () {
      obj.game.id = store.get('current_game_id');
      if (!obj.game.id) {
        obj.resetGame();
        return $q.resolve();
      }
      return $http({
        url: Config.api + '/games/' + obj.game.id,
        method: 'get'
      })
      .then(function (response) {
        var results = response.data.results;
        obj.game.players = results.players;
        obj.game.guesser = results.game.guesser;
        var my_id = store.get('remote_id');
        for (var i = 0; i < obj.game.players.length; i++) {
          if (obj.game.players[i].id === my_id) {
            obj.isCreator = obj.game.players[i].creator ? true : false;
            break;
          }
        }
        obj.game.rounds = results.rounds;
        var lastRound = obj.game.rounds[0];
        if (lastRound && lastRound.reader_id === store.get('remote_id')) {
          obj.isReader = true;
        } else {
          obj.isReader = false;
        }
        obj.game.current_round = lastRound;
        if (obj.game.current_round && obj.game.current_round.responses.length === obj.game.players.length) {
          obj.game.current_round.ready = true;
        }
        obj.started = response.data.results.game.started;
        obj.game.id = response.data.results.game.id;
        obj.game.max_score = response.data.results.game.max_score;
        socket.emit('room', obj.game.id);
      })
      .catch(function (error) {
        return error;
      })
    },

    resetGame: function () {
      obj.isReader = false;
      obj.started = false;
      obj.isCreator = false;
      obj.game.players = [];
      obj.game.rounds = [];
      obj.game.topic = '';
      obj.game.id = undefined;
      obj.game.guesser = undefined;
      obj.game.response = '';
      obj.submitting = false;
      obj.game.currentRound  = {

        reader: undefined,

        reader_id: undefined,

        //All answers in, ready to start guessing
        ready: false,

        //Array of objects, with id, text, guessed, and user_id
        responses: [

        ],

        //Current round's topic
        topic: ''
      };
      obj.game.rounds = [];
      obj.response = '';
      obj.current_round = {
        max_score: undefined,
        reader_id: undefined,
        reader_name: undefined,
        ready: false,
        response: []
      }
      obj.guess = {
        user: undefined,
        response: undefined
      }
    },

    updateGame: function  () {
      return obj.checkGame()
      .then(function (hasGame) {
        if (hasGame) {
          return obj.getGame();
        }
      })
      .catch(function (error) {
        console.log(error);
      });
    },

    startGame: function () {
      return $http({
        url: Config.api + '/games/' + obj.game.id + '/start',
        method: 'post'
      })
      .then(function (response) {
        if (response.data.started) {
          obj.started = true;
        }
      })
      .catch(function (error) {
        console.log('starting game error:', error);
      });
    },

    submitTopic: function () {
      var cacheTopic = obj.topic;
      return $http({
        url: Config.api + '/rounds/' + obj.game.current_round.id + '/topic',
        method: 'post',
        data: {topic: obj.topic}
      })
      .then(function (response) {
        if (response.data.submitted) {
          obj.topic = '';
          obj.game.current_round.topic = cacheTopic;
        }
      })
      .catch(function (error) {
        console.log('topic post error:', error);
      })
    },

    submitResponse: function () {
      var cacheResponse = obj.response;
      return $http({
        url: Config.api + '/rounds/' + obj.game.current_round.id + '/response',
        method: 'post',
        data: {
          text: obj.response,
          user_id: store.get('remote_id')
        }
      })
      .then(function (response) {
        if (response.data.submitted) {
          obj.response = '';
        }
      })
      .catch(function (error) {
        console.log('response error: ', error);
      });
    },

    submitGuess: function (guessInfo) {
      guessInfo.guesser_id = store.get('remote_id');
      return $http({
        url: Config.api + '/rounds/' + obj.game.current_round.id + '/guess',
        method: 'POST',
        data: {
          guess: guessInfo
        }
      })
      .then( function (res) {
        return res;
      })
      .catch( function (error) {
        console.error(error);
      })
      .finally(function () {
        obj.guess.user = undefined;
        obj.guess.response = undefined;
      });
    },

    getPlayer: function (id) {
      var player;
      for (var i = 0; i < obj.game.players.length; i++) {
        if (obj.game.players[i].id === id) {
          player = obj.game.players[i];
        }
      }
      return player;
    },

    amGuesser: function () {
      return obj.game.guesser && obj.game.guesser.id === store.get('remote_id');
    }
  }

  socket.on('invite response', function () {
    obj.getGame();
  });

  socket.on('start', function () {
    obj.started = true;
  });

  socket.on('round', function (round) {
    obj.game.rounds.unshift(round);
    if (round.reader_id === store.get('remote_id')) {
      obj.isReader = true;
    } else {
      obj.isReader = false;
    }
    obj.game.guesser = undefined;
    obj.game.current_round = round;
    obj.game.current_round.ready = false;
    obj.game.current_round.topic = '';
  });

  socket.on('topic', function (topic) {
    obj.game.current_round.topic = topic;
  });

  socket.on('response', function (response) {
    var match = undefined;
    if (obj.game.current_round.responses) {
      for (var i = 0; i < obj.game.current_round.responses.length; i++) {
        var checkResponse = obj.game.current_round.responses[i];
        if (response.id === checkResponse) {
          match = checkResponse;
          break;
        }
      }
    } else {
      obj.game.current_round.responses = [];
    }

    if (!match) {
      obj.game.current_round.responses.push(response);
    }

    if (obj.game.current_round && obj.game.current_round.responses.length === obj.game.players.length) {
      obj.game.current_round.ready = true;
    }
  });

  socket.on('guesser', function (guesser) {
    obj.response = '';
    obj.game.guesser = guesser;
  });

  socket.on('guess', function (guess) {
    var guessedResponse;
    var guessee;
    for (var i = 0; i < obj.game.players.length; i++) {
      var player = obj.game.players[i];
      if (player.id === guess.details.guesser_id) {
        obj.game.guesser = player;
      }
      if (player.id === guess.details.guessee_id) {
        guessee = player;
      }
      if (guess.result && guess.details.guessee_id === player.id) {
        obj.game.current_round.guesses = obj.game.current_round.guesses || {};
        obj.game.current_round.guesses[player.id] = true;
      }
    }
    if (guess.result) {
      for (var i = 0; i < obj.game.current_round.responses.length; i++) {
        var response = obj.game.current_round.responses[i];
        if (response.id === guess.details.response_id) {
          guessedResponse = response;
          response.guessed = true;
          break;
        }
      }
      obj.game.guesser.score = obj.game.guesser.score + 1;
    }
    if (obj.amGuesser()) {
      return;
    }
    var result = guess.result ? 'Correct!' : 'Wrong!'
    var guess_message = obj.game.guesser.full_name + ' guessed "' + guessedResponse.text + '" was written by ' + guessee.full_name + '. ' + result;
    ionicToast.show(guess_message, 'top', false, 2500);
  });

  return obj;

}])

.factory('socket', function (socketFactory) {
  var io_socket = io(Config.api, {'forceNew': true});
  var socket = socketFactory({
    ioSocket: io_socket
  });
  return socket;
})

.factory('Auth', ['auth', 'store', '$state', 'socket', function(auth, store, $state, socket){

  var logout = function () {
    auth.signout();
    socket.emit('logout', store.get('remote_id'));
    store.remove('profile');
    store.remove('pic_url');
    store.remove('name');
    store.remove('token');
    store.remove('fb_access_token');
    store.remove('remote_id');
    $state.go('login');
  };

  return {
    logout: logout
  };

}])

.service('BlankService', [function(){

}]);
<commit_msg>(fix) correct the logic for processing a guess on the client side<commit_after>angular.module('app.services', [])

.factory('Game', ['$q', '$http', 'store', 'socket', '$timeout', 'ionicToast', function($q, $http, store, socket, $timeout, ionicToast){

  var obj = {
    submitting: false,

    started: false,

    response: '',

    topic: '',

    isReader: false,

    isCreator: false,

    game: {

      id: undefined,

      max_score: undefined,

      guesser: undefined,

      //Array of objects, with id, name, guessed, and score
      players: [],

      //Object containing all pertinent info for the round
      current_round: {

        reader_name: undefined,

        reader_id: undefined,

        //All answers in, ready to start guessing
        ready: false,

        //Array of objects, with id, text, guessed, and user_id
        responses: [

        ],

        //Current round's topic
        topic: ''
      },

      //Array of rounds from the server
      rounds: []
    },

    // The guess a user is going to make
    guess: {
      user: undefined,
      response: undefined
    },

    checkGame: function () {
      var remote_id = store.get('remote_id');
      if (!remote_id) {
        return $q.resolve();
      }
      return $http({
        url: Config.api + '/users/' + remote_id,
        method: 'get'
      })
      .then(function (response) {
        var current_game_id = response.data.user.current_game_id;
        store.set('current_game_id', current_game_id);
        obj.game.id = current_game_id;
        return !!current_game_id;
      })
      .catch(function (error) {
        console.log('check game error', error);
      })
    },

    getGame: function () {
      obj.game.id = store.get('current_game_id');
      if (!obj.game.id) {
        obj.resetGame();
        return $q.resolve();
      }
      return $http({
        url: Config.api + '/games/' + obj.game.id,
        method: 'get'
      })
      .then(function (response) {
        var results = response.data.results;
        obj.game.players = results.players;
        obj.game.guesser = results.game.guesser;
        var my_id = store.get('remote_id');
        for (var i = 0; i < obj.game.players.length; i++) {
          if (obj.game.players[i].id === my_id) {
            obj.isCreator = obj.game.players[i].creator ? true : false;
            break;
          }
        }
        obj.game.rounds = results.rounds;
        var lastRound = obj.game.rounds[0];
        if (lastRound && lastRound.reader_id === store.get('remote_id')) {
          obj.isReader = true;
        } else {
          obj.isReader = false;
        }
        obj.game.current_round = lastRound;
        if (obj.game.current_round && obj.game.current_round.responses.length === obj.game.players.length) {
          obj.game.current_round.ready = true;
        }
        obj.started = response.data.results.game.started;
        obj.game.id = response.data.results.game.id;
        obj.game.max_score = response.data.results.game.max_score;
        socket.emit('room', obj.game.id);
      })
      .catch(function (error) {
        return error;
      })
    },

    resetGame: function () {
      obj.isReader = false;
      obj.started = false;
      obj.isCreator = false;
      obj.game.players = [];
      obj.game.rounds = [];
      obj.game.topic = '';
      obj.game.id = undefined;
      obj.game.guesser = undefined;
      obj.game.response = '';
      obj.submitting = false;
      obj.game.currentRound  = {

        reader: undefined,

        reader_id: undefined,

        //All answers in, ready to start guessing
        ready: false,

        //Array of objects, with id, text, guessed, and user_id
        responses: [

        ],

        //Current round's topic
        topic: ''
      };
      obj.game.rounds = [];
      obj.response = '';
      obj.current_round = {
        max_score: undefined,
        reader_id: undefined,
        reader_name: undefined,
        ready: false,
        response: []
      }
      obj.guess = {
        user: undefined,
        response: undefined
      }
    },

    updateGame: function  () {
      return obj.checkGame()
      .then(function (hasGame) {
        if (hasGame) {
          return obj.getGame();
        }
      })
      .catch(function (error) {
        console.log(error);
      });
    },

    startGame: function () {
      return $http({
        url: Config.api + '/games/' + obj.game.id + '/start',
        method: 'post'
      })
      .then(function (response) {
        if (response.data.started) {
          obj.started = true;
        }
      })
      .catch(function (error) {
        console.log('starting game error:', error);
      });
    },

    submitTopic: function () {
      var cacheTopic = obj.topic;
      return $http({
        url: Config.api + '/rounds/' + obj.game.current_round.id + '/topic',
        method: 'post',
        data: {topic: obj.topic}
      })
      .then(function (response) {
        if (response.data.submitted) {
          obj.topic = '';
          obj.game.current_round.topic = cacheTopic;
        }
      })
      .catch(function (error) {
        console.log('topic post error:', error);
      })
    },

    submitResponse: function () {
      var cacheResponse = obj.response;
      return $http({
        url: Config.api + '/rounds/' + obj.game.current_round.id + '/response',
        method: 'post',
        data: {
          text: obj.response,
          user_id: store.get('remote_id')
        }
      })
      .then(function (response) {
        if (response.data.submitted) {
          obj.response = '';
        }
      })
      .catch(function (error) {
        console.log('response error: ', error);
      });
    },

    submitGuess: function (guessInfo) {
      guessInfo.guesser_id = store.get('remote_id');
      return $http({
        url: Config.api + '/rounds/' + obj.game.current_round.id + '/guess',
        method: 'POST',
        data: {
          guess: guessInfo
        }
      })
      .then( function (res) {
        return res;
      })
      .catch( function (error) {
        console.error(error);
      })
      .finally(function () {
        obj.guess.user = undefined;
        obj.guess.response = undefined;
      });
    },

    getPlayer: function (id) {
      var player;
      for (var i = 0; i < obj.game.players.length; i++) {
        if (obj.game.players[i].id === id) {
          player = obj.game.players[i];
        }
      }
      return player;
    },

    amGuesser: function () {
      return obj.game.guesser && obj.game.guesser.id === store.get('remote_id');
    }
  }

  socket.on('invite response', function () {
    obj.getGame();
  });

  socket.on('start', function () {
    obj.started = true;
  });

  socket.on('round', function (round) {
    obj.game.rounds.unshift(round);
    if (round.reader_id === store.get('remote_id')) {
      obj.isReader = true;
    } else {
      obj.isReader = false;
    }
    obj.game.guesser = undefined;
    obj.game.current_round = round;
    obj.game.current_round.ready = false;
    obj.game.current_round.topic = '';
  });

  socket.on('topic', function (topic) {
    obj.game.current_round.topic = topic;
  });

  socket.on('response', function (response) {
    var match = undefined;
    if (obj.game.current_round.responses) {
      for (var i = 0; i < obj.game.current_round.responses.length; i++) {
        var checkResponse = obj.game.current_round.responses[i];
        if (response.id === checkResponse) {
          match = checkResponse;
          break;
        }
      }
    } else {
      obj.game.current_round.responses = [];
    }

    if (!match) {
      obj.game.current_round.responses.push(response);
    }

    if (obj.game.current_round && obj.game.current_round.responses.length === obj.game.players.length) {
      obj.game.current_round.ready = true;
    }
  });

  socket.on('guesser', function (guesser) {
    obj.response = '';
    obj.game.guesser = guesser;
  });

  socket.on('guess', function (guess) {
    var guessedResponse;
    var guessee;
    var guesser;
    for (var i = 0; i < obj.game.players.length; i++) {
      var player = obj.game.players[i];
      if (player.id === guess.details.guesser_id) {
        guesser = player;
      }
      if (player.id === guess.details.guessee_id) {
        guessee = player;
      }
      if (guess.result && guess.details.guessee_id === player.id) {
        obj.game.current_round.guesses = obj.game.current_round.guesses || {};
        obj.game.current_round.guesses[player.id] = true;
      }
    }
    for (var i = 0; i < obj.game.current_round.responses.length; i++) {
      var response = obj.game.current_round.responses[i];
      if (response.id === guess.details.response_id) {
        guessedResponse = response;
        if (guess.result) {
          response.guessed = true;
        }
        break;
      }
    }
    if (guess.result) {
      guesser.score = guesser.score + 1;
    }
    var result = guess.result ? 'Correct!' : 'Wrong!'
    var guess_message = guesser.full_name + ' guessed "' + guessedResponse.text + '" was written by ' + guessee.full_name + '. ' + result;
    ionicToast.show(guess_message, 'top', false, 2500);
  });

  return obj;

}])

.factory('socket', function (socketFactory) {
  var io_socket = io(Config.api, {'forceNew': true});
  var socket = socketFactory({
    ioSocket: io_socket
  });
  return socket;
})

.factory('Auth', ['auth', 'store', '$state', 'socket', function(auth, store, $state, socket){

  var logout = function () {
    auth.signout();
    socket.emit('logout', store.get('remote_id'));
    store.remove('profile');
    store.remove('pic_url');
    store.remove('name');
    store.remove('token');
    store.remove('fb_access_token');
    store.remove('remote_id');
    $state.go('login');
  };

  return {
    logout: logout
  };

}])

.service('BlankService', [function(){

}]);
<|endoftext|>