<commit_before>/* eslint no-bitwise: 0, jsx-no-bind: 0 */
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import _ from 'lodash';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import axios from 'axios';
import { Button, Icon, message, Modal, Spin, Tooltip } from 'antd';
import SplitPane from 'react-split-pane';
import { fetchFileContent, saveFile, showDemoAlert, stickTab, setViewChanged } from '../home/redux/actions';
import editorStateMap from './editorStateMap';
import modelManager from './modelManager';
import { storage } from '../common/utils';
import { codeChange } from './redux/actions';
import { OutlineResizer, MonacoEditor, EditorSider } from './';

export class CodeEditor extends Component {
  static propTypes = {
    outlineWidth: PropTypes.number.isRequired,
    elementById: PropTypes.object.isRequired,
    location: PropTypes.object.isRequired,
    fileContentById: PropTypes.object.isRequired,
    fileContentNeedReload: PropTypes.object.isRequired,
    fetchFileContentPending: PropTypes.bool.isRequired, // eslint-disable-line
    saveFilePending: PropTypes.bool.isRequired,
    viewChanged: PropTypes.object.isRequired,
    actions: PropTypes.object.isRequired,
    file: PropTypes.string.isRequired,
    onError: PropTypes.func,
    onStateChange: PropTypes.func,
    onRunTest: PropTypes.func,
  };

  static defaultProps = {
    onError() {},
    onStateChange() {},
    onRunTest: null,
  };

  constructor(props) {
    super(props);
    // BIG magic here! When open/switch a new file, it will trigger cursor change first
    // which causes to save status. So if the file is new open, should not save its state
    // before recover the state. So when file is changed set this flag to true. After recover
    // the status, set it to false so that state could be saved.
    this.preventSaveEditorState = true;

    this.monacoListeners = [];
  }

  state = {
    notFound: false,
    loadingFile: false,
    loadingEditor: true,
    cursorPos: {
      lineNumber: 1,
      column: 1,
    },
    showOutline: !storage.local.getItem('noOutline'),
  };

  async componentWillMount() {
    this.setState({
      loadingFile: true,
    });
    await this.checkAndFetchFileContent(this.props);
    // Todo: check if conflict
    modelManager.setInitialValue(this.props.file, this.getFileContent(this.props.file), true);
    this.setState({
      // eslint-disable-line
      loadingFile: false,
    });
    this.props.onStateChange({ hasChange: false });
  }

  async componentWillReceiveProps(nextProps) {
    const { props } = this;
    if (props.file !== nextProps.file) {
      // When file is changed, prevent saving before its state is restored.
      this.preventSaveEditorState = true;
      this.setState(
        {
          loadingFile: true,
        },
        () => this.editor.layout()
      );
      await this.checkAndFetchFileContent(nextProps);
      // Todo: check if conflict
      modelManager.setInitialValue(nextProps.file, this.getFileContent(nextProps.file), true);
      this.preventSaveEditorState = false;
      this.recoverEditorState();
      this.setState({
        loadingFile: false,
      });
    } else if (nextProps.fileContentNeedReload[nextProps.file]) {
      const oldContent = this.getFileContent();
      const hasChange = this.hasChange(); // has changed
      await this.checkAndFetchFileContent(nextProps);
      modelManager.setInitialValue(nextProps.file, this.getFileContent(nextProps.file), true);
      this.setState({
        loadingFile: false,
      });
      const newContent = this.getFileContent();
      if (hasChange && oldContent !== newContent && newContent !== modelManager.getValue(nextProps.file)) {
        Modal.confirm({
          title: 'The file has changed on disk.',
          content: 'Do you want to reload it?',
          okText: 'Yes',
          cancelText: 'No',
          onOk: () => {
            modelManager.reset(nextProps.file);
          },
        });
      }
    }
  }

  componentWillUnmount() {
    this.monacoListeners.forEach(lis => lis.dispose());
  }

  getFileContent(filePath) {
    return this.props.fileContentById[filePath || this.props.file];
  }

  getOutlineWidth() {
    return this.hasOutline() && this.state.showOutline ? this.props.outlineWidth : 0;
  }

  hasOutline() {
    return this.props.file.split('.').pop() === 'js';
  }

  formatCode = () => {
    this.setState({ loadingFile: true });
    const lines = this.editor.getValue().split('\n');
    const { lineNumber, column } = this.editor.getPosition();
    const cursorOffset = lines.slice(0, lineNumber - 1).reduce((c, line) => c + line.length + 1, 0) + column - 1;

    const formattingFile = this.props.file;
    axios
      .post('/rekit/api/format-code', {
        content: modelManager.getValue(this.props.file),
        file: this.props.file,
        cursorOffset,
      })
      .then(res => {
        if (this.props.file !== formattingFile) return;
        if (res.data.error || !res.data.content.formatted) {
          this.setState({ loadingFile: false });
          return;
        }
        this.editor.executeEdits('format', [
          { range: new monaco.Range(1, 1, 1000000, 1), text: res.data.content.formatted, forceMoveMarkers: true },
        ]);
        const newCursorOffset = res.data.content.cursorOffset;
        const newLines = res.data.content.formatted.split('\n');
        let c = 0;
        let newLineNumber = 0;
        while (c + newLines[newLineNumber].length + 1 < newCursorOffset && newLineNumber < newLines.length) {
          c += newLines[newLineNumber].length + 1;
          newLineNumber += 1;
        }
        const col = newCursorOffset - c;
        this.editor.setPosition({ lineNumber: newLineNumber + 1, column: col + 1 });
        this.setState({
          loadingFile: false,
        });
      })
      .catch(() => {
        this.setState({
          loadingFile: false,
        });
      });
  };

  recoverEditorState = () => {
    if (!this.editor) return;
    const { file } = this.props;
    this.preventSaveEditorState = false;
    const editorState = editorStateMap[file] || null;
    this.editor.restoreViewState(editorState);
    this.editor.focus();
  };

  hasChange() {
    // Whether the editor content is different from which in store.
    return modelManager.isChanged(this.props.file);
  }

  checkAndFetchFileContent(props) {
    // Check if content exists or need reload, if yes then fetch it.
    const { fileContentById, fileContentNeedReload, fetchFileContentPending, file } = props;
    if ((!_.has(fileContentById, file) || fileContentNeedReload[file]) && !fetchFileContentPending) {
      return this.props.actions
        .fetchFileContent(props.file)
        .then(() => {
          this.setState({ notFound: false });
        })
        .catch(e => {
          message.error(`Failed to load file: ${e.toString()}`);
          if (_.get(e, 'response.status') === 404) {
            this.setState({ notFound: true, loadingFile: false });
            this.props.onError(404);
          }
        });
    } else {
      this.setState({ notFound: false });
      return Promise.resolve();
    }
  }

  handleEditorChange = () => {
    // const isChanged = modelManager.isChanged(this.props.file);
    const hasChange = this.hasChange();
    const key = this.props.location.pathname;
    if (!!this.props.viewChanged[key] !== hasChange) {
      this.props.actions.setViewChanged(this.props.location.pathname, this.hasChange());
    }
    // this.props.actions.codeChange();
    // if (this.hasChange()) this.props.actions.stickTab();
  };

  handleOutlineSelect = nodeData => {
    if (nodeData.startLine && this.editor) {
      this.editor.setScrollTop(this.editor.getTopForLineNumber(nodeData.startLine));
    }
  };

  handleEditorDidMount = editor => {
    this.editor = editor;
    editor.focus();
    // This seems to be able to add multiple times.
    editor.addCommand([monaco.KeyMod.CtrlCmd | monaco.KeyCode.KEY_S], () => {
      this.handleSave();
    });
    editor.addCommand([monaco.KeyMod.CtrlCmd | monaco.KeyCode.KEY_O], () => {
      this.handleToggleOutline();
    });
    editor.addCommand([monaco.KeyMod.CtrlCmd | monaco.KeyCode.KEY_B], () => {
      this.formatCode();
    });
    this.monacoListeners.push(
      editor.onDidChangeCursorPosition(() => this.setState({ cursorPos: editor.getPosition() })),
      editor.onDidChangeCursorPosition(this.handleEditorCursorScrollerChange),
      editor.onDidScrollChange(this.handleEditorCursorScrollerChange)
    );
    this.setState({
      loadingEditor: false,
    });
    // // It needs some time for editor to load its content
    setTimeout(this.recoverEditorState, 30);
  };

  handleEditorCursorScrollerChange = () => {
    if (this.preventSaveEditorState) return;
    const { file } = this.props;

    editorStateMap[file] = this.editor.saveViewState();
  };

  handleRunTest = () => {
    this.props.onRunTest();
  };

  handleSave = () => {
    this.props.actions.stickTab();
    if (!this.hasChange()) return;
    this.props.actions
      .saveFile(this.props.file, this.editor.getValue())
      .then(() => {
        modelManager.setInitialValue(this.props.file, this.editor.getValue());
        this.props.actions.setViewChanged(this.props.location.pathname, false);
      })
      .catch(() => {
        if (process.env.REKIT_ENV === 'demo') {
          this.props.actions.showDemoAlert();
          return;
        }
        Modal.error({
          title: 'Failed to save.',
          content: 'Please retry or use other text editor.',
        });
      });
  };

  handleCancel = () => {
    Modal.confirm({
      content: 'Are you sure to discard your changes?',
      okText: 'Discard',
      cancelText: 'No',
      onOk: () => {
        modelManager.setValue(this.props.file, modelManager.getInitialValue(this.props.file));
      },
    });
  };

  handleToggleOutline = () => {
    this.setState(
      {
        showOutline: !this.state.showOutline,
      },
      () => {
        this.editor.layout();
        storage.local.setItem('noOutline', !this.state.showOutline);
      }
    );
  };

  render() {
    if (this.state.notFound) {
      return (

              {this.props.file}

File not found.

      );
    }
    const options = {
      selectOnLineNumbers: true,
      wrappingIndent: 'same',
    };
    const hasChange = this.hasChange();
    const { saveFilePending } = this.props;
    return (

        <OutlineResizer />

{this.props.file}

            {this.state.cursorPos.column && (

                Ln {this.state.cursorPos.lineNumber}, Col {this.state.cursorPos.column}

            )}
            <Tooltip
              overlayClassName="tooltip-no-arrow"
              title={

                  Beautify your code using prettier{' '}

                    ({/^Mac/.test(window.navigator.platform) ? 'Cmd' : 'Ctrl'}+B)

              }
            >
              <Button onClick={this.formatCode} size="small">
                <Icon type="menu-fold" />
              </Button>
            </Tooltip>
            {this.props.onRunTest && (
              <Button type="primary" onClick={this.handleRunTest} size="small">
                <Icon type="play-circle-o" /> Run test
              </Button>
            )}
            {hasChange &&
              !this.state.loadingFile && (
                <Tooltip
                  title={

                      Save{' '}

                        ({/^Mac/.test(window.navigator.platform) ? 'Cmd' : 'Ctrl'}+S)

                  }
                >
                  <Button
                    type="primary"
                    size="small"
                    loading={saveFilePending}
                    disabled={saveFilePending}
                    onClick={this.handleSave}
                  >
                    <Icon type="save" />
                  </Button>
                </Tooltip>
              )}
            {hasChange &&
              !this.state.loadingFile && (
                <Tooltip title="Discard changes">
                  <Button size="small" onClick={this.handleCancel} disabled={saveFilePending}>
                    <Icon type="close-circle" />
                  </Button>
                </Tooltip>
              )}
            {this.hasOutline() && (
              <Tooltip
                title={

                    Toggle Sider{' '}

                      ({/^Mac/.test(window.navigator.platform) ? 'Cmd' : 'Ctrl'}+O)

                }
              >
                <Button size="small" onClick={this.handleToggleOutline}>
                  <Icon type="bars" />
                </Button>
              </Tooltip>
            )}

        {(this.state.loadingFile || this.state.loadingEditor) && (
          <div className="loading-container" style={{ marginRight: `${this.getOutlineWidth()}px` }}>
            <Spin size="large" />

        )}
        <div className="monaco-editor-container" style={{ marginRight: `${this.getOutlineWidth()}px` }}>
          <MonacoEditor
            file={this.props.file}
            options={options}
            onChange={this.handleEditorChange}
            editorDidMount={this.handleEditorDidMount}
          />

        {this.editor &&
          this.hasOutline() &&
          this.state.showOutline && (
            <EditorSider
              file={this.props.file}
              code={this.editor.getValue()}
              width={this.getOutlineWidth()}
              onSelectNode={this.handleOutlineSelect}
              showDepsView={!!this.props.elementById[this.props.file]}
            />
          )}

    );
  }
}

/* istanbul ignore next */
function mapStateToProps(state) {
  return {
    // codeChange: state.home.codeChange,
    outlineWidth: state.editor.outlineWidth,
    fileContentById: state.home.fileContentById,
    elementById: state.home.elementById,
    fileContentNeedReload: state.home.fileContentNeedReload,
    fetchFileContentPending: state.home.fetchFileContentPending,
    saveFilePending: state.home.saveFilePending,
    location: state.router.location,
    viewChanged: state.home.viewChanged,
  };
}

/* istanbul ignore next */
function mapDispatchToProps(dispatch) {
  return {
    actions: bindActionCreators(
      { fetchFileContent, saveFile, codeChange, showDemoAlert, stickTab, setViewChanged },
      dispatch
    ),
  };
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(CodeEditor);
<commit_msg>Stick tab when code change.<commit_after>/* eslint no-bitwise: 0, jsx-no-bind: 0 */
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import _ from 'lodash';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import axios from 'axios';
import { Button, Icon, message, Modal, Spin, Tooltip } from 'antd';
import SplitPane from 'react-split-pane';
import { fetchFileContent, saveFile, showDemoAlert, stickTab, setViewChanged } from '../home/redux/actions';
import editorStateMap from './editorStateMap';
import modelManager from './modelManager';
import { storage } from '../common/utils';
import { codeChange } from './redux/actions';
import { OutlineResizer, MonacoEditor, EditorSider } from './';

export class CodeEditor extends Component {
  static propTypes = {
    outlineWidth: PropTypes.number.isRequired,
    elementById: PropTypes.object.isRequired,
    location: PropTypes.object.isRequired,
    fileContentById: PropTypes.object.isRequired,
    fileContentNeedReload: PropTypes.object.isRequired,
    fetchFileContentPending: PropTypes.bool.isRequired, // eslint-disable-line
    saveFilePending: PropTypes.bool.isRequired,
    viewChanged: PropTypes.object.isRequired,
    actions: PropTypes.object.isRequired,
    file: PropTypes.string.isRequired,
    onError: PropTypes.func,
    onStateChange: PropTypes.func,
    onRunTest: PropTypes.func,
  };

  static defaultProps = {
    onError() {},
    onStateChange() {},
    onRunTest: null,
  };

  constructor(props) {
    super(props);
    // BIG magic here! When open/switch a new file, it will trigger cursor change first
    // which causes to save status. So if the file is new open, should not save its state
    // before recover the state. So when file is changed set this flag to true. After recover
    // the status, set it to false so that state could be saved.
    this.preventSaveEditorState = true;

    this.monacoListeners = [];
  }

  state = {
    notFound: false,
    loadingFile: false,
    loadingEditor: true,
    cursorPos: {
      lineNumber: 1,
      column: 1,
    },
    showOutline: !storage.local.getItem('noOutline'),
  };

  async componentWillMount() {
    this.setState({
      loadingFile: true,
    });
    await this.checkAndFetchFileContent(this.props);
    // Todo: check if conflict
    modelManager.setInitialValue(this.props.file, this.getFileContent(this.props.file), true);
    this.setState({
      // eslint-disable-line
      loadingFile: false,
    });
    this.props.onStateChange({ hasChange: false });
  }

  async componentWillReceiveProps(nextProps) {
    const { props } = this;
    if (props.file !== nextProps.file) {
      // When file is changed, prevent saving before its state is restored.
      this.preventSaveEditorState = true;
      this.setState(
        {
          loadingFile: true,
        },
        () => this.editor.layout()
      );
      await this.checkAndFetchFileContent(nextProps);
      // Todo: check if conflict
      modelManager.setInitialValue(nextProps.file, this.getFileContent(nextProps.file), true);
      this.preventSaveEditorState = false;
      this.recoverEditorState();
      this.setState({
        loadingFile: false,
      });
    } else if (nextProps.fileContentNeedReload[nextProps.file]) {
      const oldContent = this.getFileContent();
      const hasChange = this.hasChange(); // has changed
      await this.checkAndFetchFileContent(nextProps);
      modelManager.setInitialValue(nextProps.file, this.getFileContent(nextProps.file), true);
      this.setState({
        loadingFile: false,
      });
      const newContent = this.getFileContent();
      if (hasChange && oldContent !== newContent && newContent !== modelManager.getValue(nextProps.file)) {
        Modal.confirm({
          title: 'The file has changed on disk.',
          content: 'Do you want to reload it?',
          okText: 'Yes',
          cancelText: 'No',
          onOk: () => {
            modelManager.reset(nextProps.file);
          },
        });
      }
    }
  }

  componentWillUnmount() {
    this.monacoListeners.forEach(lis => lis.dispose());
  }

  getFileContent(filePath) {
    return this.props.fileContentById[filePath || this.props.file];
  }

  getOutlineWidth() {
    return this.hasOutline() && this.state.showOutline ? this.props.outlineWidth : 0;
  }

  hasOutline() {
    return this.props.file.split('.').pop() === 'js';
  }

  formatCode = () => {
    this.setState({ loadingFile: true });
    const lines = this.editor.getValue().split('\n');
    const { lineNumber, column } = this.editor.getPosition();
    const cursorOffset = lines.slice(0, lineNumber - 1).reduce((c, line) => c + line.length + 1, 0) + column - 1;

    const formattingFile = this.props.file;
    axios
      .post('/rekit/api/format-code', {
        content: modelManager.getValue(this.props.file),
        file: this.props.file,
        cursorOffset,
      })
      .then(res => {
        if (this.props.file !== formattingFile) return;
        if (res.data.error || !res.data.content.formatted) {
          this.setState({ loadingFile: false });
          return;
        }
        this.editor.executeEdits('format', [
          { range: new monaco.Range(1, 1, 1000000, 1), text: res.data.content.formatted, forceMoveMarkers: true },
        ]);
        const newCursorOffset = res.data.content.cursorOffset;
        const newLines = res.data.content.formatted.split('\n');
        let c = 0;
        let newLineNumber = 0;
        while (c + newLines[newLineNumber].length + 1 < newCursorOffset && newLineNumber < newLines.length) {
          c += newLines[newLineNumber].length + 1;
          newLineNumber += 1;
        }
        const col = newCursorOffset - c;
        this.editor.setPosition({ lineNumber: newLineNumber + 1, column: col + 1 });
        this.setState({
          loadingFile: false,
        });
      })
      .catch(() => {
        this.setState({
          loadingFile: false,
        });
      });
  };

  recoverEditorState = () => {
    if (!this.editor) return;
    const { file } = this.props;
    this.preventSaveEditorState = false;
    const editorState = editorStateMap[file] || null;
    this.editor.restoreViewState(editorState);
    this.editor.focus();
  };

  hasChange() {
    // Whether the editor content is different from which in store.
    return modelManager.isChanged(this.props.file);
  }

  checkAndFetchFileContent(props) {
    // Check if content exists or need reload, if yes then fetch it.
    const { fileContentById, fileContentNeedReload, fetchFileContentPending, file } = props;
    if ((!_.has(fileContentById, file) || fileContentNeedReload[file]) && !fetchFileContentPending) {
      return this.props.actions
        .fetchFileContent(props.file)
        .then(() => {
          this.setState({ notFound: false });
        })
        .catch(e => {
          message.error(`Failed to load file: ${e.toString()}`);
          if (_.get(e, 'response.status') === 404) {
            this.setState({ notFound: true, loadingFile: false });
            this.props.onError(404);
          }
        });
    } else {
      this.setState({ notFound: false });
      return Promise.resolve();
    }
  }

  handleEditorChange = () => {
    const hasChange = this.hasChange();
    const key = this.props.location.pathname;
    if (!!this.props.viewChanged[key] !== hasChange) {
      this.props.actions.setViewChanged(this.props.location.pathname, this.hasChange());
    }
    // this.props.actions.codeChange();
    this.props.actions.stickTab();
  };

  handleOutlineSelect = nodeData => {
    if (nodeData.startLine && this.editor) {
      this.editor.setScrollTop(this.editor.getTopForLineNumber(nodeData.startLine));
    }
  };

  handleEditorDidMount = editor => {
    this.editor = editor;
    editor.focus();
    // This seems to be able to add multiple times.
    editor.addCommand([monaco.KeyMod.CtrlCmd | monaco.KeyCode.KEY_S], () => {
      this.handleSave();
    });
    editor.addCommand([monaco.KeyMod.CtrlCmd | monaco.KeyCode.KEY_O], () => {
      this.handleToggleOutline();
    });
    editor.addCommand([monaco.KeyMod.CtrlCmd | monaco.KeyCode.KEY_B], () => {
      this.formatCode();
    });
    this.monacoListeners.push(
      editor.onDidChangeCursorPosition(() => this.setState({ cursorPos: editor.getPosition() })),
      editor.onDidChangeCursorPosition(this.handleEditorCursorScrollerChange),
      editor.onDidScrollChange(this.handleEditorCursorScrollerChange)
    );
    this.setState({
      loadingEditor: false,
    });
    // // It needs some time for editor to load its content
    setTimeout(this.recoverEditorState, 30);
  };

  handleEditorCursorScrollerChange = () => {
    if (this.preventSaveEditorState) return;
    const { file } = this.props;

    editorStateMap[file] = this.editor.saveViewState();
  };

  handleRunTest = () => {
    this.props.onRunTest();
  };

  handleSave = () => {
    this.props.actions.stickTab();
    if (!this.hasChange()) return;
    this.props.actions
      .saveFile(this.props.file, this.editor.getValue())
      .then(() => {
        modelManager.setInitialValue(this.props.file, this.editor.getValue());
        this.props.actions.setViewChanged(this.props.location.pathname, false);
      })
      .catch(() => {
        if (process.env.REKIT_ENV === 'demo') {
          this.props.actions.showDemoAlert();
          return;
        }
        Modal.error({
          title: 'Failed to save.',
          content: 'Please retry or use other text editor.',
        });
      });
  };

  handleCancel = () => {
    Modal.confirm({
      content: 'Are you sure to discard your changes?',
      okText: 'Discard',
      cancelText: 'No',
      onOk: () => {
        modelManager.setValue(this.props.file, modelManager.getInitialValue(this.props.file));
      },
    });
  };

  handleToggleOutline = () => {
    this.setState(
      {
        showOutline: !this.state.showOutline,
      },
      () => {
        this.editor.layout();
        storage.local.setItem('noOutline', !this.state.showOutline);
      }
    );
  };

  render() {
    if (this.state.notFound) {
      return (

              {this.props.file}

File not found.

      );
    }
    const options = {
      selectOnLineNumbers: true,
      wrappingIndent: 'same',
    };
    const hasChange = this.hasChange();
    const { saveFilePending } = this.props;
    return (

        <OutlineResizer />

{this.props.file}

            {this.state.cursorPos.column && (

                Ln {this.state.cursorPos.lineNumber}, Col {this.state.cursorPos.column}

            )}
            <Tooltip
              overlayClassName="tooltip-no-arrow"
              title={

                  Beautify your code using prettier{' '}

                    ({/^Mac/.test(window.navigator.platform) ? 'Cmd' : 'Ctrl'}+B)

              }
            >
              <Button onClick={this.formatCode} size="small">
                <Icon type="menu-fold" />
              </Button>
            </Tooltip>
            {this.props.onRunTest && (
              <Button type="primary" onClick={this.handleRunTest} size="small">
                <Icon type="play-circle-o" /> Run test
              </Button>
            )}
            {hasChange &&
              !this.state.loadingFile && (
                <Tooltip
                  title={

                      Save{' '}

                        ({/^Mac/.test(window.navigator.platform) ? 'Cmd' : 'Ctrl'}+S)

                  }
                >
                  <Button
                    type="primary"
                    size="small"
                    loading={saveFilePending}
                    disabled={saveFilePending}
                    onClick={this.handleSave}
                  >
                    <Icon type="save" />
                  </Button>
                </Tooltip>
              )}
            {hasChange &&
              !this.state.loadingFile && (
                <Tooltip title="Discard changes">
                  <Button size="small" onClick={this.handleCancel} disabled={saveFilePending}>
                    <Icon type="close-circle" />
                  </Button>
                </Tooltip>
              )}
            {this.hasOutline() && (
              <Tooltip
                title={

                    Toggle Sider{' '}

                      ({/^Mac/.test(window.navigator.platform) ? 'Cmd' : 'Ctrl'}+O)

                }
              >
                <Button size="small" onClick={this.handleToggleOutline}>
                  <Icon type="bars" />
                </Button>
              </Tooltip>
            )}

        {(this.state.loadingFile || this.state.loadingEditor) && (
          <div className="loading-container" style={{ marginRight: `${this.getOutlineWidth()}px` }}>
            <Spin size="large" />

        )}
        <div className="monaco-editor-container" style={{ marginRight: `${this.getOutlineWidth()}px` }}>
          <MonacoEditor
            file={this.props.file}
            options={options}
            onChange={this.handleEditorChange}
            editorDidMount={this.handleEditorDidMount}
          />

        {this.editor &&
          this.hasOutline() &&
          this.state.showOutline && (
            <EditorSider
              file={this.props.file}
              code={this.editor.getValue()}
              width={this.getOutlineWidth()}
              onSelectNode={this.handleOutlineSelect}
              showDepsView={!!this.props.elementById[this.props.file]}
            />
          )}

    );
  }
}

/* istanbul ignore next */
function mapStateToProps(state) {
  return {
    // codeChange: state.home.codeChange,
    outlineWidth: state.editor.outlineWidth,
    fileContentById: state.home.fileContentById,
    elementById: state.home.elementById,
    fileContentNeedReload: state.home.fileContentNeedReload,
    fetchFileContentPending: state.home.fetchFileContentPending,
    saveFilePending: state.home.saveFilePending,
    location: state.router.location,
    viewChanged: state.home.viewChanged,
  };
}

/* istanbul ignore next */
function mapDispatchToProps(dispatch) {
  return {
    actions: bindActionCreators(
      { fetchFileContent, saveFile, codeChange, showDemoAlert, stickTab, setViewChanged },
      dispatch
    ),
  };
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(CodeEditor);
<|endoftext|>


<commit_before>// Ionic Starter App

// angular.module is a global place for creating, registering and retrieving Angular modules
// 'starter' is the name of this angular module example (also set in a <body> attribute in index.html)
// the 2nd parameter is an array of 'requires'

angular.module('careWheels', [
  'ionic',
  'ui.router',
  'ngCordova',
  'FredrikSandell.worker-pool',
  'angularMoment',
  'careWheels.fileloggermodule'
])

//contant definition for endpoint base url
.constant('BASE_URL', 'https://carewheels.cecs.pdx.edu:8443')

// change the version number here
.constant('VERSION_NUMBER', '0.06')

.run(function ($rootScope, $ionicPlatform, $ionicHistory, $state, $window, User) {

  $rootScope.$on('$stateChangeStart', function (event, next, nextParams, fromState) {
    console.log('state change');
    if (User.credentials() === null) {
      if (next.name !== 'login') {
        event.preventDefault();
        $state.go('login');
      }
    }
  });

  $ionicPlatform.registerBackButtonAction(function (event) {
    console.log("in registerbackbutton");
    console.log($ionicHistory.backTitle());
    $state.go($ionicHistory.backTitle());
  }, 100);

  $ionicPlatform.ready(function () {
    if (window.cordova && window.cordova.plugins.Keyboard) {
      cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
      cordova.plugins.Keyboard.disableScroll(true);
    }
    if (window.StatusBar) {
      StatusBar.styleDefault();
    }
  });

})

// API factory for making all php endpoints globally accessible.
.factory('API', function (BASE_URL) {
  var api = {
    userAndGroupInfo: BASE_URL + '/userandgroupmemberinfo.php',
    userInfo: BASE_URL + '/userinfo.php',
    updateUserReminders: BASE_URL + '/updateuserreminders.php',
    groupMemberInfo: BASE_URL + '/groupmemberinfo.php',
    updateLastOwnership: BASE_URL + '/updatelastownershiptakentime.php',
    creditUser: BASE_URL + '/credituser.php',
    updateSettings:BASE_URL + '/updatesettings.php'
  };
  return api;
});
<commit_msg>Version number change for recent merges.<commit_after>// Ionic Starter App

// angular.module is a global place for creating, registering and retrieving Angular modules
// 'starter' is the name of this angular module example (also set in a <body> attribute in index.html)
// the 2nd parameter is an array of 'requires'

angular.module('careWheels', [
  'ionic',
  'ui.router',
  'ngCordova',
  'FredrikSandell.worker-pool',
  'angularMoment',
  'careWheels.fileloggermodule'
])

//contant definition for endpoint base url
.constant('BASE_URL', 'https://carewheels.cecs.pdx.edu:8443')

// change the version number here
.constant('VERSION_NUMBER', '0.07')

.run(function ($rootScope, $ionicPlatform, $ionicHistory, $state, $window, User) {

  $rootScope.$on('$stateChangeStart', function (event, next, nextParams, fromState) {
    console.log('state change');
    if (User.credentials() === null) {
      if (next.name !== 'login') {
        event.preventDefault();
        $state.go('login');
      }
    }
  });

  $ionicPlatform.registerBackButtonAction(function (event) {
    console.log("in registerbackbutton");
    console.log($ionicHistory.backTitle());
    $state.go($ionicHistory.backTitle());
  }, 100);

  $ionicPlatform.ready(function () {
    if (window.cordova && window.cordova.plugins.Keyboard) {
      cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
      cordova.plugins.Keyboard.disableScroll(true);
    }
    if (window.StatusBar) {
      StatusBar.styleDefault();
    }
  });

})

// API factory for making all php endpoints globally accessible.
.factory('API', function (BASE_URL) {
  var api = {
    userAndGroupInfo: BASE_URL + '/userandgroupmemberinfo.php',
    userInfo: BASE_URL + '/userinfo.php',
    updateUserReminders: BASE_URL + '/updateuserreminders.php',
    groupMemberInfo: BASE_URL + '/groupmemberinfo.php',
    updateLastOwnership: BASE_URL + '/updatelastownershiptakentime.php',
    creditUser: BASE_URL + '/credituser.php',
    updateSettings:BASE_URL + '/updatesettings.php'
  };
  return api;
});
<|endoftext|>


<commit_before>#!/usr/local/bin/node

// run "node build.js" to generate a self-contained jsio_compile script

var sys = require('sys'),
	fs = require('fs'),
	path = require('path'),
	node = process.argv[0];

var exec = require('child_process').exec;

var BUILD_DIR = 'build';
var TARGET = path.join(BUILD_DIR, 'jsio_compile');
var CACHE_PATH = path.join(BUILD_DIR, '.cache');

require('./jsio/jsio');

// setup logging
jsio('from base import *');
logger = logging.get('compiler');
logging.get('preprocessors.compiler').setLevel(0);

jsio('import lib.Callback');
var cb = new lib.Callback();

exec('mkdir -p ' + BUILD_DIR, null, cb.chain());
exec('mkdir -p ' + CACHE_PATH, null, cb.chain());

cb.run(doCompile);

function doCompile() {
	// get access to jsio path util functions
	jsio('import util.path');

	var compiler = jsio('import preprocessors.compiler');

	var interface = jsio('import .node_interface');
	interface.logger.setLevel(0);

	compiler.setCompressor(interface.compressor);

	compiler.compile('import preprocessors.compiler');
	compiler.compile('import .compiler', {
		dynamicImports: {
			COMPILER: 'import .node_interface'
		}
	});

	// grab a full copy of jsio
	walk('jsio', function(path, files, dirs) {
		files.forEach(function(file) {
			if (/\.js$/.test(file)) {
				var module = file.replace(/^jsio\//, '').replace(/\.js$/, '').replace(/\//g, '.');
				if (module != 'jsio') {
					compiler.compile('import ' + module);
				}
			}
		});
	});

	exec("which " + node, function(error, stdout, stderr) {
		var nodeLocation = stdout.replace(/\n/g, '');
		logger.info('Found node at', nodeLocation)
		exec(nodeLocation + " --version", function(error, stdout, stderr) {
			var nodeVersion = stdout.replace(/\n/g, '');;
			logger.info('Using node version', nodeVersion);
			compiler.generateSrc({compressorCachePath: CACHE_PATH, compressSources: true, compressResult: true, preserveJsioSource: true}, function(src) {
				var fd = fs.openSync(TARGET, 'w');
				fs.writeSync(fd, '#!' + nodeLocation + '\n');
				fs.writeSync(fd, src);
				fs.writeSync(fd, 'jsio("import .compiler").start()');
				fs.closeSync(fd);

				exec("chmod +x " + TARGET);
				logger.info('Wrote', TARGET);
			});
		});
	});
}

// ********
// node-specific walk implementation

function walk(path, callback) {
	var stat = fs.statSync(path);

	if (!stat.isDirectory()) { throw new Error('walk: ' + path + ' is not a directory'); }

	var files = fs.readdirSync(path),
		items = {files: [], dirs: []};

	files.forEach(function(name) {
		var absPath = util.path.join(path, name);
		if (fs.statSync(absPath).isDirectory()) {
			if (walk(absPath, callback) == true) { return false; }
			items.dirs.push(absPath);
		} else {
			items.files.push(absPath);
		}
	});

	return callback(path, items.files, items.dirs);
}

<commit_msg>use /usr/bin/env node rather than an absolute path to node!<commit_after>#!/usr/bin/env node

// run "node build.js" to generate a self-contained jsio_compile script

var sys = require('sys'),
	fs = require('fs'),
	path = require('path'),
	node = process.argv[0];

var exec = require('child_process').exec;

var BUILD_DIR = 'build';
var TARGET = path.join(BUILD_DIR, 'jsio_compile');
var CACHE_PATH = path.join(BUILD_DIR, '.cache');

require('./jsio/jsio');

// setup logging
jsio('from base import *');
logger = logging.get('compiler');
logging.get('preprocessors.compiler').setLevel(0);

jsio('import lib.Callback');
var cb = new lib.Callback();

exec('mkdir -p ' + BUILD_DIR, null, cb.chain());
exec('mkdir -p ' + CACHE_PATH, null, cb.chain());

cb.run(doCompile);

function doCompile() {
	// get access to jsio path util functions
	jsio('import util.path');

	var compiler = jsio('import preprocessors.compiler');

	var interface = jsio('import .node_interface');
	interface.logger.setLevel(0);

	compiler.setCompressor(interface.compressor);

	compiler.compile('import preprocessors.compiler');
	compiler.compile('import .compiler', {
		dynamicImports: {
			COMPILER: 'import .node_interface'
		}
	});

	// grab a full copy of jsio
	walk('jsio', function(path, files, dirs) {
		files.forEach(function(file) {
			if (/\.js$/.test(file)) {
				var module = file.replace(/^jsio\//, '').replace(/\.js$/, '').replace(/\//g, '.');
				if (module != 'jsio') {
					compiler.compile('import ' + module);
				}
			}
		});
	});

	compiler.generateSrc({compressorCachePath: CACHE_PATH, compressSources: true, compressResult: true}, function(src) {
		var fd = fs.openSync(TARGET, 'w');
		fs.writeSync(fd, '#!/usr/bin/env node\n');
		fs.writeSync(fd, src);
		fs.writeSync(fd, 'jsio("import .compiler").start()');
		fs.closeSync(fd);

		exec("chmod +x " + TARGET);
		logger.info('Wrote', TARGET);
	});
}

// ********
// node-specific walk implementation

function walk(path, callback) {
	var stat = fs.statSync(path);

	if (!stat.isDirectory()) { throw new Error('walk: ' + path + ' is not a directory'); }

	var files = fs.readdirSync(path),
		items = {files: [], dirs: []};

	files.forEach(function(name) {
		var absPath = util.path.join(path, name);
		if (fs.statSync(absPath).isDirectory()) {
			if (walk(absPath, callback) == true) { return false; }
			items.dirs.push(absPath);
		} else {
			items.files.push(absPath);
		}
	});

	return callback(path, items.files, items.dirs);
}

<|endoftext|>


<commit_before>module.exports = {
  pattern: 'help',
  reply: function(message, match, reply) {
    const help = `
      Here's what you can ask me:
      - [search ]add-ons \`query\`
      - [search ]authors \`name\`
      - [search ]forums for \`query\`
      - [search ]wiki for \`query\`
      - [search ]source for \`query\`
      - [search ]lua for \`query\`
      - live version
      - pts version
      - joke
      - [create ]meme \`template\`; \`top line\`; \`bottom line\`
      - memes[ templates]
      - help
    `;

    reply(help.replace(/^\s+|\s+$/, '').replace(/\s+\n/, "\n"));
  }
};
<commit_msg>Embed help message into the bot<commit_after><|endoftext|>


<commit_before>var controllers = angular.module('controllers');

controllers.controller('MapCtrl', ['$scope', '$rootScope', 'Search', function ($scope, $rootScope, Search) {
    // Mapbox doesn't need its own var - it automatically attaches to Leaflet's L.
    require('mapbox.js');
    // Use Awesome Markers lib to produce font-icon map markers
    require('../../../src/leaflet.awesome-markers.js');
    // Marker clustering
    require('../../../node_modules/leaflet.markercluster/dist/leaflet.markercluster.js');

    // Initialize the map, using Affinity Bridge's mapbox account.
    var map = L.mapbox.map('mapContainer', 'affinitybridge.ia7h38nj');

    map.locate({setView: false}); // set setView to false so that map doesn't re-center on geolocation
    map.on("locationfound", function(e) {
        L.marker(e.latlng).addTo(map);
    });

    // Initialize the empty layer for the markers, and add it to the map.
    // TODO: don't use global var here
    clusterLayer = new L.MarkerClusterGroup({
        zoomToBoundsOnClick: false,
        spiderfyDistanceMultiplier: 2,
        showCoverageOnHover: false
    }).addTo(map);
    // When user clicks on a cluster, zoom directly to its bounds.  If we don't do this,
    // they have to click repeatedly to zoom in enough for the cluster to spiderfy.
    clusterLayer.on('clusterclick', function (a) {
        // Close any popups that are open already. This helps if we came via "show on map" link,
        // which spawns an unbound popup.
        map.closePopup();
        // If the markers in this cluster are all in the same place, spiderfy on click.
        var bounds = a.layer.getBounds();
        if (bounds._northEast.equals(bounds._southWest)) {
            a.layer.spiderfy();
        } else {
            // If the markers in this cluster are NOT all in the same place, zoom in on them.
            a.layer.zoomToBounds();
        }
    });

    var polygonLayer = L.geoJson();
    map.addLayer(polygonLayer);

    // Match possible Activity Categories to Humanitarian Font icons.
    // TODO: this is global right now so we can use it in the ServicesList service
    iconGlyphs = {
        'CASH': {glyph: 'ocha-sector-cash', markerColor: '#a48658' },
        'EDUCATION': {glyph: 'ocha-sector-education', markerColor: '#c00000' },
        'FOOD': {glyph: 'ocha-sector-foodsecurity', markerColor: '#006600' },
        'HEALTH': {glyph: 'ocha-sector-health', markerColor: '#08a1d9' },
        'NFI': {glyph: 'ocha-item-reliefgood', markerColor: '#f96a1b' },
        'PROTECTION': {glyph: 'ocha-sector-protection', markerColor: '#1f497d' },
        'SHELTER': {glyph: 'ocha-sector-shelter', markerColor: '#989aac' },
        'WASH': {glyph: 'ocha-sector-wash', markerColor: '#7030a0' }
    };

    // TODO: remove global
    iconObjects = {};

    // Create the icon objects. We'll reuse the same icon for all markers in the same category.
    for (var category in iconGlyphs) {
        iconObjects[category] = L.AwesomeMarkers.icon({
            icon: iconGlyphs[category].glyph,
            prefix: 'icon', // necessary because Humanitarian Fonts prefixes its icon names with "icon"
            iconColor: iconGlyphs[category].markerColor,
            markerColor: "white",
            extraClasses: category
        });
    }

    var onChange = function(event) {
        var results = Search.currResults();

        // Clear all the map markers.
        clusterLayer.clearLayers();

        // Initialize the list-view output.
        //var listOutput = '
Services
';

        // Initialize a list where we'll store the current markers for easy reference when
        // building the "show on map" functionality.  TODO: can we streamline this out?
        var markers = {};

        // Loop through the filtered results, adding the markers back to the map.
        results.forEach( function (feature) {
            // Add the filtered markers back to the map's data layer
            clusterLayer.addLayer(feature.properties.marker);
            // Store the marker for easy reference.
            markers[feature.id] = feature.properties.marker;
            // Build the output for the filtered list view
            //listOutput += renderServiceText(feature, "list");
        } );

        // Replace the contents of the list div with this new, filtered output.
        //$('#list').html(listOutput);

        // According functionality for the list - expand item when its header is clicked
        //$(".serviceText > header").click(function(event) {
        //    event.preventDefault();
        //    $(this).parent().toggleClass('expand');
        //    // Toggle the text of the "Show details" / "Hide details" link
        //    if ($(this).find('.show-details').html() === "Show details") {
        //        $(this).find('.show-details').html("Hide details");
        //    } else {
        //        $(this).find('.show-details').html("Show details");
        //    }
        //});

        // Bind "show on map" behavior.  Do this here because now the list exists.
        //$(".show-on-map").click(function(e) {
        //    // Get the unique ID of this service.
        //    var id = e.target.id;
        //    // Close any popups that are open already.
        //    map.closePopup();
        //    // Fire the map/list toggler click event, to switch to viewing the map.
        //    $("#map-list-toggler").click();
        //    // Pan and zoom the map.
        //    map.panTo(markers[id]._latlng);
        //    if (map.getZoom() < 12) { map.setZoom(12); }
        //    // Clone the popup for this marker.  We'll show it at the correct lat-long, but
        //    // unbound from the marker.  We do this in case the marker is in a cluster.
        //    var unboundPopup = markers[id].getPopup();
        //    // Send the service's unique ID as the className of the popup, so that the "Show
        //    // details" binding will work as usual when the popupopen event fires; also, offset
        //    // the Y position so the popup is a little bit above the marker or cluster.
        //    map.openPopup(L.popup({className:id, offset: new L.Point(0,-25)})
        //        .setLatLng(markers[id]._latlng)
        //        .setContent(markers[id].getPopup()._content));
        //});
    };

    $rootScope.$on('FILTER_CHANGED', onChange);
}]);
<commit_msg>Added region overlay. Not linked with search functionality yet<commit_after>var controllers = angular.module('controllers');

controllers.controller('MapCtrl', ['$scope', '$rootScope', 'Search', function ($scope, $rootScope, Search) {
    // Mapbox doesn't need its own var - it automatically attaches to Leaflet's L.
    require('mapbox.js');
    // Use Awesome Markers lib to produce font-icon map markers
    require('../../../src/leaflet.awesome-markers.js');
    // Marker clustering
    require('../../../node_modules/leaflet.markercluster/dist/leaflet.markercluster.js');

    // Initialize the map, using Affinity Bridge's mapbox account.
    var map = L.mapbox.map('mapContainer', 'affinitybridge.ia7h38nj');

    map.locate({setView: false}); // set setView to false so that map doesn't re-center on geolocation
    map.on("locationfound", function(e) {
        L.marker(e.latlng).addTo(map);
    });

    // Initialize the empty layer for the markers, and add it to the map.
    // TODO: don't use global var here
    clusterLayer = new L.MarkerClusterGroup({
        zoomToBoundsOnClick: false,
        spiderfyDistanceMultiplier: 2,
        showCoverageOnHover: false
    }).addTo(map);
    // When user clicks on a cluster, zoom directly to its bounds.  If we don't do this,
    // they have to click repeatedly to zoom in enough for the cluster to spiderfy.
    clusterLayer.on('clusterclick', function (a) {
        // Close any popups that are open already. This helps if we came via "show on map" link,
        // which spawns an unbound popup.
        map.closePopup();
        // If the markers in this cluster are all in the same place, spiderfy on click.
        var bounds = a.layer.getBounds();
        if (bounds._northEast.equals(bounds._southWest)) {
            a.layer.spiderfy();
        } else {
            // If the markers in this cluster are NOT all in the same place, zoom in on them.
            a.layer.zoomToBounds();
        }
    });

    var polygonLayer = L.geoJson();
    map.addLayer(polygonLayer);

    jQuery.getJSON( "src/polygons.json", function( polygonData ) {
        // Create the polygon layer and add to the map.
        polygonLayer.addData(polygonData);

        polygonLayer.getLayers().forEach(function(f) {
            f.setStyle({
                opacity: 0.3
            });

            L.DomEvent.addListener(f, 'mouseover', function(e) {
                //if(this._activeFilters.indexOf(polygonLayer.getLayerId(e.target)) < 0) {
                    e.target.setStyle({
                        opacity: 0.5
                    });
                //}
            }, this);

            L.DomEvent.addListener(f, 'mouseout', function(e) {
                //if(this._activeFilters.indexOf(polygonLayer.getLayerId(e.target)) < 0) {
                    e.target.setStyle({
                        opacity: 0.3
                    });
                //}
            }, this);

            //L.DomEvent.addListener(f, 'click', this._onFeatureClick, this);

            //var key = this._getFeatureId(f);
            //if (!this._filters[key]) {
            //    this._numItems++;
            //    this._filters[key] = this._addItem(f);
            //}
        });
    });

    // Match possible Activity Categories to Humanitarian Font icons.
    // TODO: this is global right now so we can use it in the ServicesList service
    iconGlyphs = {
        'CASH': {glyph: 'ocha-sector-cash', markerColor: '#a48658' },
        'EDUCATION': {glyph: 'ocha-sector-education', markerColor: '#c00000' },
        'FOOD': {glyph: 'ocha-sector-foodsecurity', markerColor: '#006600' },
        'HEALTH': {glyph: 'ocha-sector-health', markerColor: '#08a1d9' },
        'NFI': {glyph: 'ocha-item-reliefgood', markerColor: '#f96a1b' },
        'PROTECTION': {glyph: 'ocha-sector-protection', markerColor: '#1f497d' },
        'SHELTER': {glyph: 'ocha-sector-shelter', markerColor: '#989aac' },
        'WASH': {glyph: 'ocha-sector-wash', markerColor: '#7030a0' }
    };

    // TODO: remove global
    iconObjects = {};

    // Create the icon objects. We'll reuse the same icon for all markers in the same category.
    for (var category in iconGlyphs) {
        iconObjects[category] = L.AwesomeMarkers.icon({
            icon: iconGlyphs[category].glyph,
            prefix: 'icon', // necessary because Humanitarian Fonts prefixes its icon names with "icon"
            iconColor: iconGlyphs[category].markerColor,
            markerColor: "white",
            extraClasses: category
        });
    }

    var onChange = function(event) {
        var results = Search.currResults();

        // Clear all the map markers.
        clusterLayer.clearLayers();

        // Initialize the list-view output.
        //var listOutput = '
Services
';

        // Initialize a list where we'll store the current markers for easy reference when
        // building the "show on map" functionality.  TODO: can we streamline this out?
        var markers = {};

        // Loop through the filtered results, adding the markers back to the map.
        results.forEach( function (feature) {
            // Add the filtered markers back to the map's data layer
            clusterLayer.addLayer(feature.properties.marker);
            // Store the marker for easy reference.
            markers[feature.id] = feature.properties.marker;
            // Build the output for the filtered list view
        } );

        // TODO: do this zooming when someone clicks into the service detailed view

        // Bind "show on map" behavior.  Do this here because now the list exists.
        //$(".show-on-map").click(function(e) {
        //    // Get the unique ID of this service.
        //    var id = e.target.id;
        //    // Close any popups that are open already.
        //    map.closePopup();
        //    // Fire the map/list toggler click event, to switch to viewing the map.
        //    $("#map-list-toggler").click();
        //    // Pan and zoom the map.
        //    map.panTo(markers[id]._latlng);
        //    if (map.getZoom() < 12) { map.setZoom(12); }
        //    // Clone the popup for this marker.  We'll show it at the correct lat-long, but
        //    // unbound from the marker.  We do this in case the marker is in a cluster.
        //    var unboundPopup = markers[id].getPopup();
        //    // Send the service's unique ID as the className of the popup, so that the "Show
        //    // details" binding will work as usual when the popupopen event fires; also, offset
        //    // the Y position so the popup is a little bit above the marker or cluster.
        //    map.openPopup(L.popup({className:id, offset: new L.Point(0,-25)})
        //        .setLatLng(markers[id]._latlng)
        //        .setContent(markers[id].getPopup()._content));
        //});
    };

    $rootScope.$on('FILTER_CHANGED', onChange);
}]);
<|endoftext|>


<commit_before>export default {
  header: 'Slider',
  headerText: 'The v-slider component is a better visualization of the number input. It is used for gathering numerical user data.',
  components: ['v-slider'],
  examples: [{
    continuous: {
      header: 'Continuous',
      desc: 'Continuous sliders should be used when precision is not a concern.'
    },
    discrete: {
      header: 'Discrete',
      desc: 'Discrete sliders offer a thumb label that displays the exact current amount. Using the step prop you can disallow selecting values outside of steps.'
    },
    icons: {
      header: 'Icons',
      desc: 'You can add icons to the slider with the append-icon and prepend-icon props.'
    },
    editableNumericValue: {
      header: 'With an editable numeric value',
      desc: 'Sliders can be combined with other components for a better display.'
    },
    dark: {
      header: 'Dark theme',
      desc: 'Sliders also support theming.'
    },
    customColors: {
      header: 'Custom colors',
      desc: 'You can set the colors of the slider using the props color, track-color and thumb-color.'
    }
  }],
  props: {
    step: 'If greater than 0, sets step interval for ticks',
    thumbColor: 'Sets the thumb and thumb label color',
    thumbLabel: 'Show thumb label',
    ticks: '',
    trackColor: 'Sets the track fill color'
  }
}
<commit_msg>fixes #75<commit_after>export default {
  header: 'Slider',
  headerText: 'The v-slider component is a better visualization of the number input. It is used for gathering numerical user data.',
  components: ['v-slider'],
  examples: [{
    continuous: {
      header: 'Continuous',
      desc: 'Continuous sliders should be used when precision is not a concern.'
    },
    discrete: {
      header: 'Discrete',
      desc: 'Discrete sliders offer a thumb label that displays the exact current amount. Using the step prop you can disallow selecting values outside of steps.'
    },
    icons: {
      header: 'Icons',
      desc: 'You can add icons to the slider with the append-icon and prepend-icon props.'
    },
    editableNumericValue: {
      header: 'With an editable numeric value',
      desc: 'Sliders can be combined with other components for a better display.'
    },
    dark: {
      header: 'Dark theme',
      desc: 'Sliders also support theming.',
      uninverted: true
    },
    customColors: {
      header: 'Custom colors',
      desc: 'You can set the colors of the slider using the props color, track-color and thumb-color.'
    }
  }],
  props: {
    step: 'If greater than 0, sets step interval for ticks',
    thumbColor: 'Sets the thumb and thumb label color',
    thumbLabel: 'Show thumb label',
    ticks: '',
    trackColor: 'Sets the track fill color'
  }
}
<|endoftext|>


<commit_before>export const AttachmentStorage = new Mongo.Collection(
  'cfs_gridfs.attachments.files',
);
export const AvatarStorage = new Mongo.Collection('cfs_gridfs.avatars.files');

const localFSStore = process.env.ATTACHMENTS_STORE_PATH;
const storeName = 'attachments';
const defaultStoreOptions = {
  beforeWrite: fileObj => {
    if (!fileObj.isImage()) {
      return {
        type: 'application/octet-stream',
      };
    }
    return {};
  },
};
let store;
if (localFSStore) {
  // have to reinvent methods from FS.Store.GridFS and FS.Store.FileSystem
  const fs = Npm.require('fs');
  const path = Npm.require('path');
  const mongodb = Npm.require('mongodb');
  const Grid = Npm.require('gridfs-stream');
  // calulate the absolute path here, because FS.Store.FileSystem didn't expose the aboslutepath or FS.Store didn't expose api calls :(
  let pathname = localFSStore;
  /*eslint camelcase: ["error", {allow: ["__meteor_bootstrap__"]}] */

  if (!pathname && __meteor_bootstrap__ && __meteor_bootstrap__.serverDir) {
    pathname = path.join(
      __meteor_bootstrap__.serverDir,
      `../../../cfs/files/${storeName}`,
    );
  }

  if (!pathname)
    throw new Error('FS.Store.FileSystem unable to determine path');

  // Check if we have '~/foo/bar'
  if (pathname.split(path.sep)[0] === '~') {
    const homepath =
      process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE;
    if (homepath) {
      pathname = pathname.replace('~', homepath);
    } else {
      throw new Error('FS.Store.FileSystem unable to resolve "~" in path');
    }
  }

  // Set absolute path
  const absolutePath = path.resolve(pathname);

  const _FStore = new FS.Store.FileSystem(storeName, {
    path: localFSStore,
    ...defaultStoreOptions,
  });
  const GStore = {
    fileKey(fileObj) {
      const key = {
        _id: null,
        filename: null,
      };

      // If we're passed a fileObj, we retrieve the _id and filename from it.
      if (fileObj) {
        const info = fileObj._getInfo(storeName, {
          updateFileRecordFirst: false,
        });
        key._id = info.key || null;
        key.filename =
          info.name ||
          fileObj.name({ updateFileRecordFirst: false }) ||
          `${fileObj.collectionName}-${fileObj._id}`;
      }

      // If key._id is null at this point, createWriteStream will let GridFS generate a new ID
      return key;
    },
    db: undefined,
    mongoOptions: { useNewUrlParser: true },
    mongoUrl: process.env.MONGO_URL,
    init() {
      this._init(err => {
        this.inited = !err;
      });
    },
    _init(callback) {
      const self = this;
      mongodb.MongoClient.connect(self.mongoUrl, self.mongoOptions, function(
        err,
        db,
      ) {
        if (err) {
          return callback(err);
        }
        self.db = db;
        return callback(null);
      });
      return;
    },
    createReadStream(fileKey, options) {
      const self = this;
      if (!self.inited) {
        self.init();
        return undefined;
      }
      options = options || {};

      // Init GridFS
      const gfs = new Grid(self.db, mongodb);

      // Set the default streamning settings
      const settings = {
        _id: new mongodb.ObjectID(fileKey._id),
        root: `cfs_gridfs.${storeName}`,
      };

      // Check if this should be a partial read
      if (
        typeof options.start !== 'undefined' &&
        typeof options.end !== 'undefined'
      ) {
        // Add partial info
        settings.range = {
          startPos: options.start,
          endPos: options.end,
        };
      }
      return gfs.createReadStream(settings);
    },
  };
  GStore.init();
  const CRS = 'createReadStream';
  const _CRS = `_${CRS}`;
  const FStore = _FStore._transform;
  FStore[_CRS] = FStore[CRS].bind(FStore);
  FStore[CRS] = function(fileObj, options) {
    let stream;
    try {
      const localFile = path.join(
        absolutePath,
        FStore.storage.fileKey(fileObj),
      );
      const state = fs.statSync(localFile);
      if (state) {
        stream = FStore[_CRS](fileObj, options);
      }
    } catch (e) {
      // file is not there, try GridFS ?
      stream = undefined;
    }
    if (stream) return stream;
    else {
      try {
        const stream = GStore[CRS](GStore.fileKey(fileObj), options);
        return stream;
      } catch (e) {
        return undefined;
      }
    }
  }.bind(FStore);
  store = _FStore;
} else {
  store = new FS.Store.GridFS(localFSStore ? `G${storeName}` : storeName, {
    // XXX Add a new store for cover thumbnails so we don't load big images in
    // the general board view
    // If the uploaded document is not an image we need to enforce browser
    // download instead of execution. This is particularly important for HTML
    // files that the browser will just execute if we don't serve them with the
    // appropriate `application/octet-stream` MIME header which can lead to user
    // data leaks. I imagine other formats (like PDF) can also be attack vectors.
    // See https://github.com/wekan/wekan/issues/99
    // XXX Should we use `beforeWrite` option of CollectionFS instead of
    // collection-hooks?
    // We should use `beforeWrite`.
    ...defaultStoreOptions,
  });
}
Attachments = new FS.Collection('attachments', {
  stores: [store],
});

if (Meteor.isServer) {
  Meteor.startup(() => {
    Attachments.files._ensureIndex({ cardId: 1 });
  });

  Attachments.allow({
    insert(userId, doc) {
      return allowIsBoardMember(userId, Boards.findOne(doc.boardId));
    },
    update(userId, doc) {
      return allowIsBoardMember(userId, Boards.findOne(doc.boardId));
    },
    remove(userId, doc) {
      return allowIsBoardMember(userId, Boards.findOne(doc.boardId));
    },
    // We authorize the attachment download either:
    // - if the board is public, everyone (even unconnected) can download it
    // - if the board is private, only board members can download it
    download(userId, doc) {
      const board = Boards.findOne(doc.boardId);
      if (board.isPublic()) {
        return true;
      } else {
        return board.hasMember(userId);
      }
    },

    fetch: ['boardId'],
  });
}

// XXX Enforce a schema for the Attachments CollectionFS

if (Meteor.isServer) {
  Attachments.files.after.insert((userId, doc) => {
    // If the attachment doesn't have a source field
    // or its source is different than import
    if (!doc.source || doc.source !== 'import') {
      // Add activity about adding the attachment
      Activities.insert({
        userId,
        type: 'card',
        activityType: 'addAttachment',
        attachmentId: doc._id,
        // this preserves the name so that notifications can be meaningful after
        // this file is removed
        attachmentName: doc.original.name,
        boardId: doc.boardId,
        cardId: doc.cardId,
        listId: doc.listId,
        swimlaneId: doc.swimlaneId,
      });
    } else {
      // Don't add activity about adding the attachment as the activity
      // be imported and delete source field
      Attachments.update(
        {
          _id: doc._id,
        },
        {
          $unset: {
            source: '',
          },
        },
      );
    }
  });

  Attachments.files.before.remove((userId, doc) => {
    Activities.insert({
      userId,
      type: 'card',
      activityType: 'deleteAttachment',
      attachmentId: doc._id,
      // this preserves the name so that notifications can be meaningful after
      // this file is removed
      attachmentName: doc.original.name,
      boardId: doc.boardId,
      cardId: doc.cardId,
      listId: doc.listId,
      swimlaneId: doc.swimlaneId,
    });
  });
}

export default Attachments;
<commit_msg>Ref: Attachments to use modern gridfs (rm localstore)<commit_after>import { Meteor } from 'meteor/meteor';
import { FilesCollection } from 'meteor/ostrio:files';
import { createBucket } from './lib/grid/createBucket';
import { createOnAfterUpload } from './lib/fsHooks/createOnAfterUpload';
import { createInterceptDownload } from './lib/fsHooks/createInterceptDownload';
import { createOnAfterRemove } from './lib/fsHooks/createOnAfterRemove';

const attachmentBucket = createBucket('attachments');

// XXX Enforce a schema for the Attachments FilesCollection
// see: https://github.com/VeliovGroup/Meteor-Files/wiki/Schema

export const Attachments = new FilesCollection({
  debug: false, // Change to `true` for debugging
  collectionName: 'attachments',
  allowClientCode: false,
  onAfterUpload(doc) {
    // If the attachment doesn't have a source field
    // or its source is different than import
    if (!doc.source || doc.source !== 'import') {
      // Add activity about adding the attachment
      Activities.insert({
        userId,
        type: 'card',
        activityType: 'addAttachment',
        attachmentId: doc._id,
        // this preserves the name so that notifications can be meaningful after
        // this file is removed
        attachmentName: doc.original.name,
        boardId: doc.boardId,
        cardId: doc.cardId,
        listId: doc.listId,
        swimlaneId: doc.swimlaneId,
      });
    } else {
      // Don't add activity about adding the attachment as the activity
      // be imported and delete source field
      Attachments.update(
        {
          _id: doc._id,
        },
        {
          $unset: {
            source: '',
          },
        },
      );
    }
    createOnAfterUpload(attachmentBucket)(doc);
  },
  interceptDownload: createInterceptDownload(attachmentBucket),
  onAfterRemove(docs) {
    docs.forEach(function(doc) {
      Activities.insert({
        userId: doc.userId,
        type: 'card',
        activityType: 'deleteAttachment',
        attachmentId: doc._id,
        // this preserves the name so that notifications can be meaningful after
        // this file is removed
        attachmentName: doc.original.name,
        boardId: doc.boardId,
        cardId: doc.cardId,
        listId: doc.listId,
        swimlaneId: doc.swimlaneId,
      });
    });
    createOnAfterRemove(attachmentBucket)(docs);
  },
  // We authorize the attachment download either:
  // - if the board is public, everyone (even unconnected) can download it
  // - if the board is private, only board members can download it
  downloadCallback(doc) {
    const board = Boards.findOne(doc.boardId);
    if (board.isPublic()) {
      return true;
    } else {
      return board.hasMember(this.userId);
    }
  },
});

if (Meteor.isServer) {
  Attachments.allow({
    insert(userId, doc) {
      return allowIsBoardMember(userId, Boards.findOne(doc.boardId));
    },
    update(userId, doc) {
      return allowIsBoardMember(userId, Boards.findOne(doc.boardId));
    },
    remove(userId, doc) {
      return allowIsBoardMember(userId, Boards.findOne(doc.boardId));
    },
    fetch: ['boardId'],
  });

  Meteor.startup(() => {
    Attachments.collection._ensureIndex({ cardId: 1 });
  });
}

export default Attachments;
<|endoftext|>


<commit_before>var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");

function Player(x, y) {
    this.x = x;
    this.y = y;
    this.width = 30;
    this.height = 30;
    this.velX = 0;
    this.velY = 0;
    this.avatar = "";
}

Player.prototype.update = function(){
    if(this.x > 590) {
      this.x = -20;
    }
    if(this.y > 590) {
      this.y = -20;
    }
    if(this.x < -20) {
      this.x = 590;
    }
    if(this.y < -20) {
      this.y = 590;
    }

    this.x += this.velX;
    this.y += this.velY;
}

Player.prototype.render = function(){
    ctx.strokeStyle = "#000";
    ctx.strokeRect(this.x, this.y, this.width, this.height);
}

var player1 = new Player(50, 50);

function Enemy(x, y) {
  this.x = x;
  this.y = y;
  this.width = 40;
  this.height = 40;
  this.velX = -1 + Math.random() * 2;
  this.velY = -1 + Math.random() * 2;
  this.avatar = document.getElementById("trump");
  this.ticks = 0;
}

Enemy.prototype.update = function() {
  this.ticks++;
  var self = this;
  if(this.x > 560) {
    this.x = 560;
  }
  if(this.y > 560) {
    this.y = 560;
  }
  if(this.x < 0) {
    this.x = 0;
  }
  if(this.y < 0) {
    this.y = 0;
  }

  if(this.ticks % 80 === 0) {
    setTimeout(function() {
      self.velX = -1 + Math.random() * 2;
      self.velY = -1 + Math.random() * 2;
    }, 2000);
  }

  this.x += this.velX;
  this.y += this.velY;
};

Enemy.prototype.render = function(){
    ctx.strokeStyle = "red";
    ctx.strokeRect(this.x, this.y, this.width, this.height);
    ctx.drawImage(this.avatar, this.x, this.y, this.width, this.height)
}

var enemy1 = new Enemy(100 + Math.random() * 400, 100 + Math.random() * 400);
var enemy2 = new Enemy(100 + Math.random() * 400, 100 + Math.random() * 400);
var enemy3 = new Enemy(100 + Math.random() * 400, 100 + Math.random() * 400);

function gameLoop(){
    ctx.fillStyle = "#FFFFFF";
    ctx.fillRect(0,0,600,600);

    player1.update();
    player1.render();

    enemy1.update();
    enemy1.render();
    enemy2.update();
    enemy2.render();
    enemy3.update();
    enemy3.render();

    window.requestAnimationFrame(gameLoop);
}

gameLoop();

document.addEventListener("keydown", function(e){
    if (e.keyCode === 39) player1.velX = 5;
    if (e.keyCode === 37) player1.velX = -5;
    if (e.keyCode === 38) player1.velY = -5;
    if (e.keyCode === 40) player1.velY = 5;
});
 document.addEventListener("keyup", function(e){
    if (e.keyCode === 39) player1.velX = 0;
    if (e.keyCode === 37) player1.velX = 0;
    if (e.keyCode === 38) player1.velY = 0;
    if (e.keyCode === 40) player1.velY = 0;
});
<commit_msg>improve enemy movement<commit_after>var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");

function Player(x, y) {
    this.x = x;
    this.y = y;
    this.width = 30;
    this.height = 30;
    this.velX = 0;
    this.velY = 0;
    this.avatar = "";
}

Player.prototype.update = function() {
    if (this.x > 590) {
        this.x = -20;
    }
    if (this.y > 590) {
        this.y = -20;
    }
    if (this.x < -20) {
        this.x = 590;
    }
    if (this.y < -20) {
        this.y = 590;
    }

    this.x += this.velX;
    this.y += this.velY;
}

Player.prototype.render = function() {
    ctx.strokeStyle = "#000";
    ctx.strokeRect(this.x, this.y, this.width, this.height);
}

var player1 = new Player(50, 50);

function Enemy(x, y) {
    this.x = x;
    this.y = y;
    this.width = 40;
    this.height = 40;
    this.velX = -2 + Math.random() * 4;
    this.velY = -2 + Math.random() * 4;
    this.avatar = document.getElementById("trump");
    this.ticks = 0;
}

Enemy.prototype.update = function() {
    this.ticks++;
    var self = this;
    if (this.x > 590) {
        this.x = -20;
    }
    if (this.y > 590) {
        this.y = -20;
    }
    if (this.x < -20) {
        this.x = 590;
    }
    if (this.y < -20) {
        this.y = 590;
    }

    if (this.ticks % 80 === 0) {
        setTimeout(function() {
            self.velX = -2 + Math.random() * 4;
            self.velY = -2 + Math.random() * 4;
        }, (Math.random() * 2000) + 1000);
    }

    this.x += this.velX;
    this.y += this.velY;
};

Enemy.prototype.render = function() {
    ctx.strokeStyle = "red";
    ctx.strokeRect(this.x, this.y, this.width, this.height);
    ctx.drawImage(this.avatar, this.x, this.y, this.width, this.height)
}

var enemy1 = new Enemy(100 + Math.random() * 400, 100 + Math.random() * 400);
var enemy2 = new Enemy(100 + Math.random() * 400, 100 + Math.random() * 400);
var enemy3 = new Enemy(100 + Math.random() * 400, 100 + Math.random() * 400);

function gameLoop() {
    ctx.fillStyle = "#FFFFFF";
    ctx.fillRect(0, 0, 600, 600);

    player1.update();
    player1.render();

    enemy1.update();
    enemy1.render();
    enemy2.update();
    enemy2.render();
    enemy3.update();
    enemy3.render();

    window.requestAnimationFrame(gameLoop);
}

gameLoop();

document.addEventListener("keydown", function(e) {
    if (e.keyCode === 39) player1.velX = 5;
    if (e.keyCode === 37) player1.velX = -5;
    if (e.keyCode === 38) player1.velY = -5;
    if (e.keyCode === 40) player1.velY = 5;
});
document.addEventListener("keyup", function(e) {
    if (e.keyCode === 39) player1.velX = 0;
    if (e.keyCode === 37) player1.velX = 0;
    if (e.keyCode === 38) player1.velY = 0;
    if (e.keyCode === 40) player1.velY = 0;
});
<|endoftext|>


<commit_before>/**
 * Created by cin on 1/18/14.
 */
/**
 * Created by cin on 1/18/14.
 */
var fs = require('fs'),
	path = require('path'),
	_ = require('underscore'),
	chance = new (require('chance'))(),
	Promise = require('bluebird'),
	errors = require('../lib/errors'),
	syBookshelf = require('./base'),
	User = require('./user'),
	Users = User.Set,
	CoStatus = require('./co-status'),
	UserCooperation = require('./user-cooperation'),
	UserCooperations = UserCooperation.Set,
	GroupMember = require('./group-membership'),
	GroupMembers = GroupMember.Set,
	CoComment = require('./co-comment'),
	Cooperation, Cooperations,
	config = require('../config'),
	avatarDir = config.cooperationAvatarDir,
	avatarExt = config.avatarExt,
	fkStatus = 'statusid',
	fkCooperation = 'cooperationid',
	fkOwner = 'ownerid';

Cooperation = module.exports = syBookshelf.Model.extend({
	tableName: 'cooperations',
	fields: [
		'id', 'name', 'description', 'company', 'avatar', 'statusid', 'ownerid', 'isprivate', 'regdeadline'
	],

	toJSON: function () {
		var ret = Cooperation.__super__.toJSON.apply(this, arguments);
		if (this.get('avatar')) {
			ret['avatar'] = Cooperation.getAvatarURI(this.id);
		}
		return ret;
	},

	fetch: function () {
		return Cooperation.__super__.fetch.apply(this, arguments)
			.then(function (cooperation) {
				if (!cooperation) return cooperation;
				return cooperation.countComments();
			});
	},

	saving: function () {
		return Cooperation.__super__
			.saving.apply(this, arguments);
	},

	usership: function () {
		return this.hasMany(UserCooperations, fkCooperation);
	},

	countUsership: function () {
		var self = this;
		UserCooperations.forge().query()
			.where(fkCooperation, '=', self.id)
			.count('id')
			.then(function (d) {
				return self.data('numUsership', d[0]["count(`id`)"]);
			});
	},

	status: function () {
		return this.belongsTo(CoStatus, fkStatus);
	},
	user: function () {
		return this.belongsTo(User, fkOwner);
	},

	cocomments: function () {
		return this.hasMany(CoComment, 'cooperationid');
	},

	countComments: function () {
		var self = this;
		return this.cocomments().fetch()
			.then(function (cocomments) {
				var numComments = cocomments.length;
				return self.data('numComments', numComments);
			});
	},
	updateAvatar: function (tmp) {
		var file = Cooperation.getAvatarPath(this.id),
			self = this;
		return new Promise(
			function (resolve, reject) {
				fs.readFile(tmp, function (err, data) {
					if (err) return reject(errors[30000]);
					fs.writeFile(file, data, function (err) {
						if (err) return reject(errors[30001]);
						resolve(self);
					});
				});
			}).then(function () {
				return self.set('avatar', self.id).save()
					.then(function () {
						return self;
					});
			}).catch(function (err) {
				return self.set('avatar', null).save()
					.then(function () {
						return Promise.rejected(err);
					});
			})
	}
}, {
	randomForge: function () {
		var status = _.random(1, 2);
		return Cooperation.forge({
			'name': chance.word(),
			'description': chance.paragraph(),
			'ownerid': chance.integer({
				min: 1,
				max: 20
			}),
			'company': chance.word(),
			'avatar': chance.word(),
			'statusid': status,
			'isprivate': chance.bool(),
			'regdeadline': chance.date({ year: 2013 })
		});
	},

	find: function (query) {
		var forCooperation = ['id', 'name', 'company', 'statusid'],
			cooperations = Cooperations.forge();
		return cooperations
			.query(function (qb) {
				_.each(forCooperation, function (k) {
					if (k in query) {
						qb.where(k, query[k]);
					}
				});
			}).query(function (qb) {
				query['orders'].forEach(function (order) {
					qb.orderBy(order[0], order[1]);
				});
			}).query('offset', query['offset'])
			.query('limit', query['limit'])
			.fetch({
				withRelated: ['user.profile', 'status']
			});
	},

	search: function (query) {
		var count = 0;
		return Cooperations.forge()
			.query(function (qb) {
				['name', 'description'].forEach(function (k) {
					if (k in query) {
						count++;
						qb.where(k, query[k]);
					}
				});
				['ownerid'].forEach(function (k) {
					if (k in query) {
						count++;
						qb.where(k, 'like', '%' + query[k] + '%');
					}
				});
			}).query(function (qb) {
				query['orders'].forEach(function (order) {
					qb.orderBy(order[0], order[1]);
				});
			}).query('offset', query['offset'])
			.query('limit', count ? query['limit'] : 0)
			.fetch({
				withRelated: ['user.profile', 'status']
			});
	},

	view: function (query) {
		return Cooperation.forge({ id: query['id'] })
			.fetch({
				withRelated: ['user.profile', 'cocomments.user.profile']
			}).then(function (cooperation) {
				if (!cooperation) return Promise.rejected(errors[20603]);
				return cooperation;
			});
	},

	getAvatarName: function (id) {
		return id + avatarExt;
	},
	getAvatarPath: function (id) {
		return path.join(avatarDir, Cooperation.getAvatarName(id));
	},
	getAvatarURI: function (id) {
		return '/cooperations/' + Cooperation.getAvatarName(id);
	}
});

Cooperations = Cooperation.Set = syBookshelf.Collection.extend({
	model: Cooperation,

	fetch: function () {
		return Cooperations.__super__.fetch.apply(this, arguments)
			.then(function (collection) {
				return collection.invokeThen('fetch');
			});
	}
});<commit_msg>update cooperation<commit_after>/**
 * Created by cin on 1/18/14.
 */
/**
 * Created by cin on 1/18/14.
 */
var fs = require('fs'),
	path = require('path'),
	_ = require('underscore'),
	chance = new (require('chance'))(),
	Promise = require('bluebird'),
	errors = require('../lib/errors'),
	syBookshelf = require('./base'),
	User = require('./user'),
	Users = User.Set,
	CoStatus = require('./co-status'),
	UserCooperation = require('./user-cooperation'),
	UserCooperations = UserCooperation.Set,
	GroupMember = require('./group-membership'),
	GroupMembers = GroupMember.Set,
	CoComment = require('./co-comment'),
	Cooperation, Cooperations,
	config = require('../config'),
	avatarDir = config.cooperationAvatarDir,
	avatarExt = config.avatarExt,
	fkStatus = 'statusid',
	fkCooperation = 'cooperationid',
	fkOwner = 'ownerid';

Cooperation = module.exports = syBookshelf.Model.extend({
	tableName: 'cooperations',
	fields: [
		'id', 'name', 'description', 'company', 'avatar', 'statusid', 'ownerid', 'isprivate', 'regdeadline'
	],

	withRelated: ['user.profile', 'status'],

	toJSON: function () {
		var ret = Cooperation.__super__.toJSON.apply(this, arguments);
		if (this.get('avatar')) {
			ret['avatar'] = Cooperation.getAvatarURI(this.id);
		}
		return ret;
	},

	fetch: function () {
		return Cooperation.__super__.fetch.apply(this, arguments)
			.then(function (cooperation) {
				if (!cooperation) return cooperation;
				return cooperation.countComments();
			});
	},

	saving: function () {
		return Cooperation.__super__
			.saving.apply(this, arguments);
	},

	usership: function () {
		return this.hasMany(UserCooperations, fkCooperation);
	},

	countUsership: function () {
		var self = this;
		UserCooperations.forge().query()
			.where(fkCooperation, '=', self.id)
			.count('id')
			.then(function (d) {
				return self.data('numUsership', d[0]["count(`id`)"]);
			});
	},

	status: function () {
		return this.belongsTo(CoStatus, fkStatus);
	},
	user: function () {
		return this.belongsTo(User, fkOwner);
	},

	cocomments: function () {
		return this.hasMany(CoComment, 'cooperationid');
	},

	countComments: function () {
		var self = this;
		return this.cocomments().fetch()
			.then(function (cocomments) {
				var numComments = cocomments.length;
				return self.data('numComments', numComments);
			});
	},
	updateAvatar: function (tmp) {
		var file = Cooperation.getAvatarPath(this.id),
			self = this;
		return new Promise(
			function (resolve, reject) {
				fs.readFile(tmp, function (err, data) {
					if (err) return reject(errors[30000]);
					fs.writeFile(file, data, function (err) {
						if (err) return reject(errors[30001]);
						resolve(self);
					});
				});
			}).then(function () {
				return self.set('avatar', self.id).save()
					.then(function () {
						return self;
					});
			}).catch(function (err) {
				return self.set('avatar', null).save()
					.then(function () {
						return Promise.rejected(err);
					});
			})
	}
}, {
	randomForge: function () {
		var status = _.random(1, 2);
		return Cooperation.forge({
			'name': chance.word(),
			'description': chance.paragraph(),
			'ownerid': chance.integer({
				min: 1,
				max: 20
			}),
			'company': chance.word(),
			'avatar': chance.word(),
			'statusid': status,
			'isprivate': chance.bool(),
			'regdeadline': chance.date({ year: 2013 })
		});
	},

	find: function (query) {
		var forCooperation = ['id', 'name', 'company', 'statusid'],
			cooperations = Cooperations.forge();
		return cooperations
			.query(function (qb) {
				_.each(forCooperation, function (k) {
					if (k in query) {
						qb.where(k, query[k]);
					}
				});
			}).query(function (qb) {
				query['orders'].forEach(function (order) {
					qb.orderBy(order[0], order[1]);
				});
			}).query('offset', query['offset'])
			.query('limit', query['limit'])
			.fetch();
	},

	search: function (query) {
		var count = 0;
		return Cooperations.forge()
			.query(function (qb) {
				['name', 'description'].forEach(function (k) {
					if (k in query) {
						count++;
						qb.where(k, query[k]);
					}
				});
				['ownerid'].forEach(function (k) {
					if (k in query) {
						count++;
						qb.where(k, 'like', '%' + query[k] + '%');
					}
				});
			}).query(function (qb) {
				query['orders'].forEach(function (order) {
					qb.orderBy(order[0], order[1]);
				});
			}).query('offset', query['offset'])
			.query('limit', count ? query['limit'] : 0)
			.fetch();
	},

	view: function (query) {
		return Cooperation.forge({ id: query['id'] })
			.fetch().then(function (cooperation) {
				if (!cooperation) return Promise.rejected(errors[20603]);
				return cooperation;
			});
	},

	getAvatarName: function (id) {
		return id + avatarExt;
	},
	getAvatarPath: function (id) {
		return path.join(avatarDir, Cooperation.getAvatarName(id));
	},
	getAvatarURI: function (id) {
		return '/cooperations/' + Cooperation.getAvatarName(id);
	}
});

Cooperations = Cooperation.Set = syBookshelf.Collection.extend({
	model: Cooperation,

	fetch: function () {
		return Cooperations.__super__.fetch.apply(this, arguments)
			.then(function (collection) {
				return collection.invokeThen('fetch');
			});
	}
});<|endoftext|>


<commit_before>$.STRATUS = function (){

	var _init = function () {
		console.log('Loaded')
		$('#logout').click(_logout)
	};

	var _makeBasicAuth = function(user, password) {
		  var token = user + ':' + password;
		  var hash = Base64.encode(token);
		  return "Basic " + hash;
	};

	var _logout = function (event) {
		console.log('Logout called')
		event.preventDefault();

		var auth = _makeBasicAuth('invalid','');
		$.ajax({
		    url : 'https://192.168.56.101:8445/pswd/',
		    method : 'GET',
		    beforeSend : function(req) {
		        req.setRequestHeader('Authorization', auth);
		    }
		});
	};

	return {
		init: _init,
	};
}();

$(document).ready($.STRATUS.init);
<commit_msg>Fix #1035: Add user logout user mechanism<commit_after>$.STRATUS = function (){

	var _init = function () {
		$('#logout').click(_logout)
	};

	var _logout = function (event) {
		event.preventDefault();
		// Workaround to logout user
		// As HTTP is state-less there is no cross-browser clean way to do
		$.get(location.href.replace('://', '://x-pdisk-logout@'));
	};

	return {
		init: _init,
	};
}();

$(document).ready($.STRATUS.init);
<|endoftext|>