<commit_before>import React, {Component, PropTypes} from 'react';
import {connect} from 'react-redux';
import c from 'classnames';
import {cloneDeep} from 'lodash';
import Paper from 'material-ui/Paper';
import FlatButton from 'material-ui/FlatButton';
import Dialog from 'material-ui/Dialog';
import IconMenu from 'material-ui/IconMenu';
import MoreVertIcon from 'material-ui/svg-icons/navigation/more-vert';
import MenuItem from 'material-ui/MenuItem';
import IconButton from 'material-ui/IconButton';
import LinearProgress from 'material-ui/LinearProgress';

import {setSnackBarParams} from './../../redux/modules/ui';
import {addFolder, listFolders} from './../../redux/modules/folder';
import AddFolderForm from './../../components/AddFolderForm/AddFolderForm';
import Pagination from './../../components/Pagination/Pagination';
import TopBar from './../../components/TopBar/TopBar';
import Heading from './../Heading/Heading';
import getFontSize from './../../helpers/getFontSize';

import sortFolderContentFields from './../../main/helpers/sortFolderContentFields';
import injectF from './../../helpers/injectF';
import injectPush from './../../helpers/injectPush';
import resolve from './../../helpers/resolve';

const styles = require('./PageFolders.scss');

@connect(({main, folder}) => ({
  interfaceFontSizeScalingFactor: main.get('interfaceFontSizeScalingFactor'),
  perpage: folder.get('perpage'),
  folders: folder.get('folders'),
  importingFolderId: folder.get('importingFolderId'),
  folderCount: folder.get('folderCount')
}), {listFolders, addFolder, setSnackBarParams})
@injectPush
@injectF
@resolve(({dispatch}, {perpage}) => {
  return dispatch(listFolders({page: 1, perpage}));
})
export default class PageFolders extends Component {

  static propTypes = {
    push: PropTypes.func.isRequired,
    f: PropTypes.func.isRequired,
    perpage: PropTypes.number.isRequired,
    addFolder: PropTypes.func.isRequired,
    folders: PropTypes.array.isRequired,
    folderCount: PropTypes.number.isRequired,
    listFolders: PropTypes.func.isRequired,
    importingFolderId: PropTypes.number,
    interfaceFontSizeScalingFactor: PropTypes.number.isRequired,
    setSnackBarParams: PropTypes.func.isRequired
  };

  constructor(props) {
    super(props);
    this.state = {
      page: 1,
      targetLanguages: [],
      isAddFolderDialogOpen: false
    };
  }

  componentWillUpdate(nextProps, nextState) {
    const {page} = this.state;
    const {perpage, listFolders} = this.props;
    if ((page !== nextState.page) || (perpage !== nextProps.perpage)) {
      listFolders({
        page: nextState.page,
        perpage: nextProps.perpage
      });
    }
  }

  openAddFolderDialog = () => {
    this.setState({
      targetLanguages: [],
      isAddFolderDialogOpen: true
    });
  };

  handleAddFolderDialogClose = () => this.setState({isAddFolderDialogOpen: false});

  handleSubmit = async (data) => {

    const {page} = this.state;
    const {f, addFolder, listFolders,
      perpage, setSnackBarParams} = this.props;

    data.contentFields = sortFolderContentFields(data.contentFields);

    await addFolder(data);
    await listFolders({page, perpage});

    setSnackBarParams(true, f('folder-has-been-created', {folderName: data.folderName}));
    this.setState({isAddFolderDialogOpen: false});
  };

  handleCancelButtonTouchTap = () => this.setState({isAddFolderDialogOpen: false});

  handleTargetLanguagesChange = (rawData) => {
    const data = cloneDeep(rawData);
    delete data.preventDefault;
    this.setState({targetLanguages: Object.values(data)});
  };

  renderAddFolderDialog() {

    const {targetLanguages, isAddFolderDialogOpen} = this.state;
    const {f} = this.props;

    return (
      <Dialog title={f('add-a-folder')} open={isAddFolderDialogOpen}
        bodyStyle={{paddingLeft: '8px', paddingRight: '8px', paddingBottom: '8px'}} autoScrollBodyContent
        onRequestClose={this.handleAddFolderDialogClose}>
        <AddFolderForm ref="addFolderForm" onSubmit={this.handleSubmit} targetLanguages={targetLanguages}
          onTargetLanguagesChange={this.handleTargetLanguagesChange}
          onCancelButtonTouchTap={this.handleCancelButtonTouchTap} />
      </Dialog>
    );
  }

  handleFolderAnchorTouchTap = (folderId) => {
    return () => this.props.push(`/folders/${folderId}/entries`);
  };

  handleFolderMenuItemTouchTap = (event, value) => {
    const {type, id} = value;
    switch (type) {
      case 'edit':
        return this.props.push(`/folders/${id}/edit`);
      case 'export':
      default:
    }
  };

  renderFolders() {
    const {f, folders, importingFolderId} = this.props;
    const rows = folders.map((folder) => {
      const {id, name} = folder;
      const isImporting = (importingFolderId === folder.id);
      return (
        <Paper className={styles.folder} key={`paper-${id}`}>
          {isImporting && <LinearProgress mode="indeterminate" style={{marginBottom: '7px'}} />}
          {name}
          {(! isImporting) && <IconMenu className={styles.folderIconMenu} style={{display: 'block', position: 'absolute'}}
            iconButtonElement={<IconButton><MoreVertIcon /></IconButton>}
            onChange={this.handleFolderMenuItemTouchTap}
            anchorOrigin={{horizontal: 'left', vertical: 'top'}}
            targetOrigin={{horizontal: 'left', vertical: 'top'}}>
            <MenuItem primaryText={f('edit')} value={{type: 'edit', id}} />
            <MenuItem primaryText={f('export')} value={{type: 'export', id}} />
          </IconMenu>}
        </Paper>
      );
    });
    return
{rows}
;
  }

  handlePageButtonTouchTap = (page) => this.setState({page});

  render() {

    const {page} = this.state;
    const {f, perpage, folderCount, interfaceFontSizeScalingFactor} = this.props;
    const buttonFontSize = getFontSize(interfaceFontSizeScalingFactor, 0.9);

    return (

        <TopBar>
          <Heading>{f('folders')}</Heading>
          <FlatButton icon={} labelStyle={{fontSize: buttonFontSize}}
            label={f('add-folder')} primary onTouchTap={this.openAddFolderDialog} />
        </TopBar>
        {this.renderFolders()}
        {(folderCount > perpage) && <Pagination current={page} total={Math.ceil(folderCount / perpage)} onButtonTouchTap={this.handlePageButtonTouchTap} />}
        {this.renderAddFolderDialog()}

    );
  }
}
<commit_msg>apply interfaceFontSizeScalingFactor to folder anchor<commit_after>import React, {Component, PropTypes} from 'react';
import {connect} from 'react-redux';
import c from 'classnames';
import {cloneDeep} from 'lodash';
import Paper from 'material-ui/Paper';
import FlatButton from 'material-ui/FlatButton';
import Dialog from 'material-ui/Dialog';
import IconMenu from 'material-ui/IconMenu';
import MoreVertIcon from 'material-ui/svg-icons/navigation/more-vert';
import MenuItem from 'material-ui/MenuItem';
import IconButton from 'material-ui/IconButton';
import LinearProgress from 'material-ui/LinearProgress';

import {setSnackBarParams} from './../../redux/modules/ui';
import {addFolder, listFolders} from './../../redux/modules/folder';
import AddFolderForm from './../../components/AddFolderForm/AddFolderForm';
import Pagination from './../../components/Pagination/Pagination';
import TopBar from './../../components/TopBar/TopBar';
import Heading from './../Heading/Heading';
import getFontSize from './../../helpers/getFontSize';

import sortFolderContentFields from './../../main/helpers/sortFolderContentFields';
import injectF from './../../helpers/injectF';
import injectPush from './../../helpers/injectPush';
import resolve from './../../helpers/resolve';

const styles = require('./PageFolders.scss');

@connect(({main, folder}) => ({
  interfaceFontSizeScalingFactor: main.get('interfaceFontSizeScalingFactor'),
  perpage: folder.get('perpage'),
  folders: folder.get('folders'),
  importingFolderId: folder.get('importingFolderId'),
  folderCount: folder.get('folderCount')
}), {listFolders, addFolder, setSnackBarParams})
@injectPush
@injectF
@resolve(({dispatch}, {perpage}) => {
  return dispatch(listFolders({page: 1, perpage}));
})
export default class PageFolders extends Component {

  static propTypes = {
    push: PropTypes.func.isRequired,
    f: PropTypes.func.isRequired,
    perpage: PropTypes.number.isRequired,
    addFolder: PropTypes.func.isRequired,
    folders: PropTypes.array.isRequired,
    folderCount: PropTypes.number.isRequired,
    listFolders: PropTypes.func.isRequired,
    importingFolderId: PropTypes.number,
    interfaceFontSizeScalingFactor: PropTypes.number.isRequired,
    setSnackBarParams: PropTypes.func.isRequired
  };

  constructor(props) {
    super(props);
    this.state = {
      page: 1,
      targetLanguages: [],
      isAddFolderDialogOpen: false
    };
  }

  componentWillUpdate(nextProps, nextState) {
    const {page} = this.state;
    const {perpage, listFolders} = this.props;
    if ((page !== nextState.page) || (perpage !== nextProps.perpage)) {
      listFolders({
        page: nextState.page,
        perpage: nextProps.perpage
      });
    }
  }

  openAddFolderDialog = () => {
    this.setState({
      targetLanguages: [],
      isAddFolderDialogOpen: true
    });
  };

  handleAddFolderDialogClose = () => this.setState({isAddFolderDialogOpen: false});

  handleSubmit = async (data) => {

    const {page} = this.state;
    const {f, addFolder, listFolders,
      perpage, setSnackBarParams} = this.props;

    data.contentFields = sortFolderContentFields(data.contentFields);

    await addFolder(data);
    await listFolders({page, perpage});

    setSnackBarParams(true, f('folder-has-been-created', {folderName: data.folderName}));
    this.setState({isAddFolderDialogOpen: false});
  };

  handleCancelButtonTouchTap = () => this.setState({isAddFolderDialogOpen: false});

  handleTargetLanguagesChange = (rawData) => {
    const data = cloneDeep(rawData);
    delete data.preventDefault;
    this.setState({targetLanguages: Object.values(data)});
  };

  renderAddFolderDialog() {

    const {targetLanguages, isAddFolderDialogOpen} = this.state;
    const {f} = this.props;

    return (
      <Dialog title={f('add-a-folder')} open={isAddFolderDialogOpen}
        bodyStyle={{paddingLeft: '8px', paddingRight: '8px', paddingBottom: '8px'}} autoScrollBodyContent
        onRequestClose={this.handleAddFolderDialogClose}>
        <AddFolderForm ref="addFolderForm" onSubmit={this.handleSubmit} targetLanguages={targetLanguages}
          onTargetLanguagesChange={this.handleTargetLanguagesChange}
          onCancelButtonTouchTap={this.handleCancelButtonTouchTap} />
      </Dialog>
    );
  }

  handleFolderAnchorTouchTap = (folderId) => {
    return () => this.props.push(`/folders/${folderId}/entries`);
  };

  handleFolderMenuItemTouchTap = (event, value) => {
    const {type, id} = value;
    switch (type) {
      case 'edit':
        return this.props.push(`/folders/${id}/edit`);
      case 'export':
      default:
    }
  };

  renderFolders() {
    const {f, folders, importingFolderId, interfaceFontSizeScalingFactor} = this.props;
    const linkFontSize = getFontSize(interfaceFontSizeScalingFactor, 1);
    const rows = folders.map((folder) => {
      const {id, name} = folder;
      const isImporting = (importingFolderId === folder.id);
      return (
        <Paper className={styles.folder} key={`paper-${id}`}>
          {isImporting && <LinearProgress mode="indeterminate" style={{marginBottom: '7px'}} />}
          {name}
          {(! isImporting) && <IconMenu className={styles.folderIconMenu} style={{display: 'block', position: 'absolute'}}
            iconButtonElement={<IconButton><MoreVertIcon /></IconButton>}
            onChange={this.handleFolderMenuItemTouchTap}
            anchorOrigin={{horizontal: 'left', vertical: 'top'}}
            targetOrigin={{horizontal: 'left', vertical: 'top'}}>
            <MenuItem primaryText={f('edit')} value={{type: 'edit', id}} />
            <MenuItem primaryText={f('export')} value={{type: 'export', id}} />
          </IconMenu>}
        </Paper>
      );
    });
    return
{rows}
;
  }

  handlePageButtonTouchTap = (page) => this.setState({page});

  render() {

    const {page} = this.state;
    const {f, perpage, folderCount, interfaceFontSizeScalingFactor} = this.props;
    const buttonFontSize = getFontSize(interfaceFontSizeScalingFactor, 0.9);

    return (

        <TopBar>
          <Heading>{f('folders')}</Heading>
          <FlatButton icon={} labelStyle={{fontSize: buttonFontSize}}
            label={f('add-folder')} primary onTouchTap={this.openAddFolderDialog} />
        </TopBar>
        {this.renderFolders()}
        {(folderCount > perpage) && <Pagination current={page} total={Math.ceil(folderCount / perpage)} onButtonTouchTap={this.handlePageButtonTouchTap} />}
        {this.renderAddFolderDialog()}

    );
  }
}
<|endoftext|>


<commit_before>import React, {Component, PropTypes} from 'react';
import {connect} from 'react-redux';
import c from 'classnames';
import {cloneDeep} from 'lodash';
import Paper from 'material-ui/Paper';
import FlatButton from 'material-ui/FlatButton';
import Dialog from 'material-ui/Dialog';
import IconMenu from 'material-ui/IconMenu';
import MoreVertIcon from 'material-ui/svg-icons/navigation/more-vert';
import MenuItem from 'material-ui/MenuItem';
import IconButton from 'material-ui/IconButton';

import {setAddFolderDialogOpen, setSnackBarParams, setTargetLanguages} from './../../redux/modules/ui';
import objToArr from './../../helpers/objToArr';
import {addFolder, listFolders, setFolderPage} from './../../redux/modules/folder';
import AddFolderForm from './../../components/AddFolderForm/AddFolderForm';
import Pagination from './../../components/Pagination/Pagination';
import TopBar from './../../components/TopBar/TopBar';

import sortContentFields from './../../helpers/sortContentFields';
import injectF from './../../helpers/injectF';
import injectPush from './../../helpers/injectPush';
import resolve from './../../helpers/resolve';

const styles = require('./PageFolders.scss');

@connect(({ui, folder}) => ({
  targetLanguages: ui.get('targetLanguages'),
  page: folder.get('page'),
  perpage: folder.get('perpage'),
  folders: folder.get('folders'),
  folderCount: folder.get('folderCount'),
  isAddFolderDialogOpen: ui.get('isAddFolderDialogOpen')
}), {listFolders, setAddFolderDialogOpen, addFolder, setFolderPage, setSnackBarParams, setTargetLanguages})
@injectPush
@injectF
@resolve(({dispatch}, {page, perpage}) => {
  return dispatch(listFolders({page, perpage}));
})
export default class PageFolders extends Component {

  static propTypes = {
    push: PropTypes.func.isRequired,
    f: PropTypes.func.isRequired,
    page: PropTypes.number.isRequired,
    perpage: PropTypes.number.isRequired,
    setFolderPage: PropTypes.func.isRequired,
    setTargetLanguages: PropTypes.func.isRequired,
    targetLanguages: PropTypes.array.isRequired,
    addFolder: PropTypes.func.isRequired,
    folders: PropTypes.array.isRequired,
    folderCount: PropTypes.number.isRequired,
    listFolders: PropTypes.func.isRequired,
    isAddFolderDialogOpen: PropTypes.bool.isRequired,
    setAddFolderDialogOpen: PropTypes.func.isRequired,
    setSnackBarParams: PropTypes.func.isRequired
  };

  componentWillReceiveProps(nextProps) {
    const {page, perpage, listFolders} = this.props;
    if ((page !== nextProps.page) || (perpage !== nextProps.perpage)) {
      listFolders({
        page: nextProps.page,
        perpage: nextProps.perpage
      });
    }
  }

  openAddFolderDialog = () => {
    this.props.setTargetLanguages([]);
    this.props.setAddFolderDialogOpen(true);
  };

  handleAddFolderDialogClose = () => this.props.setAddFolderDialogOpen(false);

  handleSubmit = async (data) => {

    const {f, addFolder, setAddFolderDialogOpen, listFolders,
      page, perpage, setSnackBarParams} = this.props;

    data.contentFields = sortContentFields(data.contentFields);

    await addFolder(data);
    await listFolders({page, perpage});

    setSnackBarParams(true, f('folder-has-been-created', {folderName: data.folderName}));
    setAddFolderDialogOpen(false);
  };

  handleCancelButtonTouchTap = () => this.props.setAddFolderDialogOpen(false);

  handleTargetLanguagesChange = (rawData) => {
    const data = cloneDeep(rawData);
    delete data.preventDefault;
    this.props.setTargetLanguages(objToArr(data));
  };

  renderAddFolderDialog() {

    const {isAddFolderDialogOpen, f, targetLanguages} = this.props;

    return (
      <Dialog title={f('add-a-folder')} open={isAddFolderDialogOpen}
        bodyStyle={{paddingLeft: '8px', paddingRight: '8px', paddingBottom: '8px'}}
        onRequestClose={this.handleAddFolderDialogClose}>
        <AddFolderForm ref="addFolderForm" onSubmit={this.handleSubmit} targetLanguages={targetLanguages}
          onTargetLanguagesChange={this.handleTargetLanguagesChange}
          onCancelButtonTouchTap={this.handleCancelButtonTouchTap} />
      </Dialog>
    );
  }

  handleFolderAnchorTouchTap = (folderId) => {
    return () => this.props.push(`/folders/${folderId}/entries`);
  };

  handleFolderMenuItemTouchTap = (event, value) => {
    const {type, id} = value;
    switch (type) {
      case 'edit':
        return this.props.push(`/folders/${id}/edit`);
      case 'export':
      default:
    }
  };

  renderFolders() {
    const {f, folders} = this.props;

    return folders.map((folder) => {
      const {id, name} = folder;
      return (
        <Paper className={styles.folder} key={`paper-${id}`}>
          {name}
          <IconMenu className={styles.folderIconMenu} style={{display: 'block', position: 'absolute'}}
            iconButtonElement={<IconButton><MoreVertIcon /></IconButton>}
            onChange={this.handleFolderMenuItemTouchTap}
            anchorOrigin={{horizontal: 'left', vertical: 'top'}}
            targetOrigin={{horizontal: 'left', vertical: 'top'}}>
            <MenuItem primaryText={f('edit')} value={{type: 'edit', id}} />
            <MenuItem primaryText={f('export')} value={{type: 'export', id}} />
          </IconMenu>
        </Paper>
      );
    });
  }

  handlePageButtonTouchTap = (page) => this.props.setFolderPage(page);

  render() {

    const {f, page, perpage, folderCount} = this.props;

    return (

        <TopBar>

{f('folders')}

          <FlatButton icon={}
            label={f('add-folder')} primary onTouchTap={this.openAddFolderDialog} />
        </TopBar>
        {this.renderFolders()}
        {(folderCount > perpage) && <Pagination current={page} total={Math.ceil(folderCount / perpage)} onButtonTouchTap={this.handlePageButtonTouchTap} />}
        {this.renderAddFolderDialog()}

    );
  }
}
<commit_msg>setFolderPage when componentWillMount<commit_after>import React, {Component, PropTypes} from 'react';
import {connect} from 'react-redux';
import c from 'classnames';
import {cloneDeep} from 'lodash';
import Paper from 'material-ui/Paper';
import FlatButton from 'material-ui/FlatButton';
import Dialog from 'material-ui/Dialog';
import IconMenu from 'material-ui/IconMenu';
import MoreVertIcon from 'material-ui/svg-icons/navigation/more-vert';
import MenuItem from 'material-ui/MenuItem';
import IconButton from 'material-ui/IconButton';

import {setAddFolderDialogOpen, setSnackBarParams, setTargetLanguages} from './../../redux/modules/ui';
import objToArr from './../../helpers/objToArr';
import {addFolder, listFolders, setFolderPage} from './../../redux/modules/folder';
import AddFolderForm from './../../components/AddFolderForm/AddFolderForm';
import Pagination from './../../components/Pagination/Pagination';
import TopBar from './../../components/TopBar/TopBar';

import sortContentFields from './../../helpers/sortContentFields';
import injectF from './../../helpers/injectF';
import injectPush from './../../helpers/injectPush';
import resolve from './../../helpers/resolve';

const styles = require('./PageFolders.scss');

@connect(({ui, folder}) => ({
  targetLanguages: ui.get('targetLanguages'),
  page: folder.get('page'),
  perpage: folder.get('perpage'),
  folders: folder.get('folders'),
  folderCount: folder.get('folderCount'),
  isAddFolderDialogOpen: ui.get('isAddFolderDialogOpen')
}), {listFolders, setAddFolderDialogOpen, addFolder, setFolderPage, setSnackBarParams, setTargetLanguages})
@injectPush
@injectF
@resolve(({dispatch}, {page, perpage}) => {
  return dispatch(listFolders({page, perpage}));
})
export default class PageFolders extends Component {

  static propTypes = {
    push: PropTypes.func.isRequired,
    f: PropTypes.func.isRequired,
    page: PropTypes.number.isRequired,
    perpage: PropTypes.number.isRequired,
    setFolderPage: PropTypes.func.isRequired,
    setTargetLanguages: PropTypes.func.isRequired,
    targetLanguages: PropTypes.array.isRequired,
    addFolder: PropTypes.func.isRequired,
    folders: PropTypes.array.isRequired,
    folderCount: PropTypes.number.isRequired,
    listFolders: PropTypes.func.isRequired,
    isAddFolderDialogOpen: PropTypes.bool.isRequired,
    setAddFolderDialogOpen: PropTypes.func.isRequired,
    setSnackBarParams: PropTypes.func.isRequired
  };

  componentWillMount() {
    this.props.setFolderPage(1);
  }

  componentWillReceiveProps(nextProps) {
    const {page, perpage, listFolders} = this.props;
    if ((page !== nextProps.page) || (perpage !== nextProps.perpage)) {
      listFolders({
        page: nextProps.page,
        perpage: nextProps.perpage
      });
    }
  }

  openAddFolderDialog = () => {
    this.props.setTargetLanguages([]);
    this.props.setAddFolderDialogOpen(true);
  };

  handleAddFolderDialogClose = () => this.props.setAddFolderDialogOpen(false);

  handleSubmit = async (data) => {

    const {f, addFolder, setAddFolderDialogOpen, listFolders,
      page, perpage, setSnackBarParams} = this.props;

    data.contentFields = sortContentFields(data.contentFields);

    await addFolder(data);
    await listFolders({page, perpage});

    setSnackBarParams(true, f('folder-has-been-created', {folderName: data.folderName}));
    setAddFolderDialogOpen(false);
  };

  handleCancelButtonTouchTap = () => this.props.setAddFolderDialogOpen(false);

  handleTargetLanguagesChange = (rawData) => {
    const data = cloneDeep(rawData);
    delete data.preventDefault;
    this.props.setTargetLanguages(objToArr(data));
  };

  renderAddFolderDialog() {

    const {isAddFolderDialogOpen, f, targetLanguages} = this.props;

    return (
      <Dialog title={f('add-a-folder')} open={isAddFolderDialogOpen}
        bodyStyle={{paddingLeft: '8px', paddingRight: '8px', paddingBottom: '8px'}}
        onRequestClose={this.handleAddFolderDialogClose}>
        <AddFolderForm ref="addFolderForm" onSubmit={this.handleSubmit} targetLanguages={targetLanguages}
          onTargetLanguagesChange={this.handleTargetLanguagesChange}
          onCancelButtonTouchTap={this.handleCancelButtonTouchTap} />
      </Dialog>
    );
  }

  handleFolderAnchorTouchTap = (folderId) => {
    return () => this.props.push(`/folders/${folderId}/entries`);
  };

  handleFolderMenuItemTouchTap = (event, value) => {
    const {type, id} = value;
    switch (type) {
      case 'edit':
        return this.props.push(`/folders/${id}/edit`);
      case 'export':
      default:
    }
  };

  renderFolders() {
    const {f, folders} = this.props;

    return folders.map((folder) => {
      const {id, name} = folder;
      return (
        <Paper className={styles.folder} key={`paper-${id}`}>
          {name}
          <IconMenu className={styles.folderIconMenu} style={{display: 'block', position: 'absolute'}}
            iconButtonElement={<IconButton><MoreVertIcon /></IconButton>}
            onChange={this.handleFolderMenuItemTouchTap}
            anchorOrigin={{horizontal: 'left', vertical: 'top'}}
            targetOrigin={{horizontal: 'left', vertical: 'top'}}>
            <MenuItem primaryText={f('edit')} value={{type: 'edit', id}} />
            <MenuItem primaryText={f('export')} value={{type: 'export', id}} />
          </IconMenu>
        </Paper>
      );
    });
  }

  handlePageButtonTouchTap = (page) => this.props.setFolderPage(page);

  render() {

    const {f, page, perpage, folderCount} = this.props;

    return (

        <TopBar>

{f('folders')}

          <FlatButton icon={}
            label={f('add-folder')} primary onTouchTap={this.openAddFolderDialog} />
        </TopBar>
        {this.renderFolders()}
        {(folderCount > perpage) && <Pagination current={page} total={Math.ceil(folderCount / perpage)} onButtonTouchTap={this.handlePageButtonTouchTap} />}
        {this.renderAddFolderDialog()}

    );
  }
}
<|endoftext|>


<commit_before>(function(angular) {
    'use strict';

    angular.module('esri.map').directive('esriMap', function($q, $timeout, esriRegistry) {

        // update two-way bound scope properties based on map state
        function updateScopeFromMap(scope, map) {
            var geoCenter = map.geographicExtent && map.geographicExtent.getCenter();
            if (geoCenter) {
                geoCenter = geoCenter.normalize();
                scope.center = {
                    lat: geoCenter.y,
                    lng: geoCenter.x
                };
            }
            scope.zoom = map.getZoom();
        }

        return {
            // element only
            restrict: 'E',

            // isolate scope
            scope: {
                // two-way binding for center/zoom
                // because map pan/zoom can change these
                center: '=?',
                zoom: '=?',
                itemInfo: '=?',
                // one-way binding for other properties
                basemap: '@',
                // function binding for event handlers
                load: '&',
                extentChange: '&',
                // function binding for reading object hash from attribute string
                // or from scope object property
                // see Example 7 here: https://gist.github.com/CMCDragonkai/6282750
                mapOptions: '&'
            },

            // replace tag with div with same id
            compile: function($element, $attrs) {
                // remove the id attribute from the main element
                $element.removeAttr('id');

                // append a new div inside this element, this is where we will create our map
                $element.append('

');

                // since we are using compile we need to return our linker function
                // the 'link' function handles how our directive responds to changes in $scope
                /*jshint unused: false*/
                return function(scope, element, attrs, controller) {};
            },

            // directive api
            controller: function($scope, $element, $attrs) {
                // get a reference to the controller
                var self = this;

                // only do this once per directive
                // this deferred will be resolved with the map
                var mapDeferred = $q.defer();

                // add this map to the registry
                if ($attrs.registerAs) {
                    var deregister = esriRegistry._register($attrs.registerAs, mapDeferred);

                    // remove this from the registry when the scope is destroyed
                    $scope.$on('$destroy', deregister);
                }

                require(['esri/map', 'esri/arcgis/utils', 'esri/geometry/Extent', 'esri/dijit/Popup'], function(Map, arcgisUtils, Extent, Popup) {
                    // setup our mapOptions based on object hash from attribute string
                    // or from scope object property
                    var mapOptions = $scope.mapOptions() || {};

                    // construct optional Extent for mapOptions
                    if (mapOptions.hasOwnProperty('extent')) {
                        // construct if parent controller isn't supplying a valid and already constructed Extent
                        // e.g. if the controller or HTML view are only providing JSON
                        if (mapOptions.extent.declaredClass !== 'esri.geometry.Extent') {
                            mapOptions.extent = new Extent(mapOptions.extent);
                        }
                    }

                    // construct optional infoWindow from mapOptions
                    // default to a new Popup dijit for now
                    // mapOptions.infoWindow expects:
                    //  {options: <Object>, srcNodeRef: <Node | String>}
                    if (mapOptions.hasOwnProperty('infoWindow')) {
                        if (mapOptions.infoWindow.hasOwnProperty('srcNodeRef')) {
                            mapOptions.infoWindow = new Popup(mapOptions.infoWindow.options || {}, mapOptions.infoWindow.srcNodeRef);
                        }
                    }

                    // check for 1 way bound properties (basemap)
                    // $scope.basemap takes precedence over $scope.mapOptions.basemap
                    if ($scope.basemap) {
                        mapOptions.basemap = $scope.basemap;
                    }

                    // check for 2 way bound properties (center and zoom)
                    // $scope.center takes precedence over $scope.mapOptions.center
                    if ($scope.center) {
                        if ($scope.center.lng && $scope.center.lat) {
                            mapOptions.center = [$scope.center.lng, $scope.center.lat];
                        } else {
                            mapOptions.center = $scope.center;
                        }
                    }

                    // $scope.zoom takes precedence over $scope.mapOptions.zoom
                    if ($scope.zoom) {
                        mapOptions.zoom = $scope.zoom;
                    }

                    // initialize map and resolve the deferred
                    if ($attrs.webmapId) {
                        // load map object from web map
                        arcgisUtils.createMap($attrs.webmapId, $attrs.id, {
                            mapOptions: mapOptions
                        }).then(function(response) {
                            // update layer infos for legend
                            self.layerInfos = arcgisUtils.getLegendLayers(response);
                            // add item info to scope
                            $scope.itemInfo = response.itemInfo;
                            // resolve the promise with the map
                            mapDeferred.resolve(response.map);
                        });
                    } else {
                        // create a new map object
                        var map = new Map($attrs.id, mapOptions);
                        // resolve the promise with the map
                        mapDeferred.resolve(map);
                    }

                    mapDeferred.promise.then(function(map) {
                        if (map.loaded) {
                            // map already loaded, we need to
                            // update two-way bound scope properties
                            updateScopeFromMap($scope, map);
                            // make map object available to caller
                            // by calling the load event handler
                            if ($attrs.load) {
                                $scope.load()(map);
                            }
                        } else {
                            // map is not yet loaded, this means that
                            // two-way bound scope properties
                            // will be updated by extent-change handler below
                            // so don't need to update them here
                            // just set up a handler for the map load event (if any)
                            if ($attrs.load) {
                                map.on('load', function() {
                                    $scope.$apply(function() {
                                        $scope.load()(map);
                                    });
                                });
                            }
                        }

                        // listen for changes to $scope.basemap and update map
                        $scope.$watch('basemap', function(newBasemap, oldBasemap) {
                            if (map.loaded && newBasemap !== oldBasemap) {
                                map.setBasemap(newBasemap);
                            }
                        });

                        // listen for changes to $scope.center and $scope.zoom and update map
                        $scope.inUpdateCycle = false;
                        if (!angular.isUndefined($scope.center) || !angular.isUndefined($scope.zoom)) {
                            $scope.$watchGroup(['center.lng', 'center.lat', 'zoom'], function(newCenterZoom/*, oldCenterZoom*/) {
                                if ($scope.inUpdateCycle) {
                                    return;
                                }
                                if (newCenterZoom[0] !== '' && newCenterZoom[1] !== '' && newCenterZoom[2] !== '') {
                                    // prevent circular updates between $watch and $apply
                                    $scope.inUpdateCycle = true;
                                    map.centerAndZoom([newCenterZoom[0], newCenterZoom[1]], newCenterZoom[2]).then(function() {
                                        $scope.inUpdateCycle = false;
                                    });
                                }
                            });
                        }

                        // listen for changes to map extent and
                        // call extent-change handler (if any)
                        // also update $scope.center and $scope.zoom
                        map.on('extent-change', function(e) {
                            if ($attrs.extentChange) {
                                $scope.extentChange()(e);
                            }
                            // prevent circular updates between $watch and $apply
                            if ($scope.inUpdateCycle) {
                                return;
                            }
                            $scope.inUpdateCycle = true;
                            $scope.$apply(function() {
                                // update scope properties
                                updateScopeFromMap($scope, map);
                                $timeout(function() {
                                    // this will be executed after the $digest cycle
                                    $scope.inUpdateCycle = false;
                                }, 0);
                            });
                        });

                        // clean up
                        $scope.$on('$destroy', function() {
                            map.destroy();
                        });
                    });
                });

                // method returns the promise that will be resolved with the map
                this.getMap = function() {
                    return mapDeferred.promise;
                };

                // adds the layer, returns the promise that will be resolved with the result of map.addLayer
                this.addLayer = function(layer, index) {
                    // layer: valid JSAPI layer
                    // index: optional <Number>; likely only used internally by, for example, esriFeatureLayer
                    return this.getMap().then(function(map) {
                        return map.addLayer(layer, index);
                    });
                };

                // support removing layers, e.g. when esriFeatureLayer goes out of scope
                this.removeLayer = function (layer) {
                    return this.getMap().then(function (map) {
                        return map.removeLayer(layer);
                    });
                };

                // array to store layer info, needed for legend
                this.addLayerInfo = function(lyrInfo) {
                    if (!this.layerInfos) {
                        this.layerInfos = [lyrInfo];
                    } else {
                        this.layerInfos.unshift(lyrInfo);
                    }
                };
                this.getLayerInfos = function() {
                    return this.layerInfos;
                };
            }
        };
    });

})(angular);
<commit_msg>contrllerAs and bindToController for map directive<commit_after>(function(angular) {
    'use strict';

    angular.module('esri.map').directive('esriMap', function($q, $timeout, esriRegistry) {

        // update two-way bound scope properties based on map state
        function updateScopeFromMap(scope, map) {
            var geoCenter = map.geographicExtent && map.geographicExtent.getCenter();
            if (geoCenter) {
                geoCenter = geoCenter.normalize();
                scope.center = {
                    lat: geoCenter.y,
                    lng: geoCenter.x
                };
            }
            scope.zoom = map.getZoom();
        }

        // handle to deregister the map if registered w/ esriRegistry
        var deregister;

        return {
            // element only
            restrict: 'E',

            // isolate scope
            scope: {
                // two-way binding for center/zoom
                // because map pan/zoom can change these
                center: '=?',
                zoom: '=?',
                itemInfo: '=?',
                // one-way binding for other properties
                basemap: '@',
                // function binding for event handlers
                load: '&',
                extentChange: '&',
                // function binding for reading object hash from attribute string
                // or from scope object property
                // see Example 7 here: https://gist.github.com/CMCDragonkai/6282750
                mapOptions: '&'
            },

            controllerAs: 'mapCtrl',

            bindToController: true,

            // replace tag with div with same id
            compile: function($element, $attrs) {

                // remove the id attribute from the main element
                $element.removeAttr('id');

                // append a new div inside this element, this is where we will create our map
                $element.append('

');

                // since we are using compile we need to return our linker function
                // the 'link' function handles how our directive responds to changes in $scope
                return function(scope, element, attrs, controller) {

                    controller.getMap().then(function(map) {
                        if (map.loaded) {
                            // map already loaded, we need to
                            // update two-way bound scope properties
                            // updateScopeFromMap(scope, map);
                            updateScopeFromMap(controller, map);
                            // make map object available to caller
                            // by calling the load event handler
                            if (controller.load) {
                                controller.load()(map);
                            }
                        } else {
                            // map is not yet loaded, this means that
                            // two-way bound scope properties
                            // will be updated by extent-change handler below
                            // so don't need to update them here
                            // just set up a handler for the map load event (if any)
                            if (attrs.load) {
                                map.on('load', function() {
                                    scope.$apply(function() {
                                        controller.load()(map);
                                    });
                                });
                            }
                        }

                        // listen for changes to scope.basemap and update map
                        scope.$watch('mapCtrl.basemap', function(newBasemap, oldBasemap) {
                            if (map.loaded && newBasemap !== oldBasemap) {
                                map.setBasemap(newBasemap);
                            }
                        });

                        // listen for changes to scope.center and scope.zoom and update map
                        scope.inUpdateCycle = false;
                        if (!angular.isUndefined(controller.center) || !angular.isUndefined(controller.zoom)) {
                            scope.$watchGroup(['mapCtrl.center.lng', 'mapCtrl.center.lat', 'mapCtrl.zoom'], function(newCenterZoom/*, oldCenterZoom*/) {
                                if (scope.inUpdateCycle) {
                                    return;
                                }
                                if (newCenterZoom[0] !== '' && newCenterZoom[1] !== '' && newCenterZoom[2] !== '') {
                                    // prevent circular updates between $watch and $apply
                                    scope.inUpdateCycle = true;
                                    map.centerAndZoom([newCenterZoom[0], newCenterZoom[1]], newCenterZoom[2]).then(function() {
                                        scope.inUpdateCycle = false;
                                    });
                                }
                            });
                        }

                        // listen for changes to map extent and
                        // call extent-change handler (if any)
                        // also update scope.center and scope.zoom
                        map.on('extent-change', function(e) {
                            if (attrs.extentChange) {
                                controller.extentChange()(e);
                            }
                            // prevent circular updates between $watch and $apply
                            if (scope.inUpdateCycle) {
                                return;
                            }
                            scope.inUpdateCycle = true;
                            scope.$apply(function() {
                                // update scope properties
                                updateScopeFromMap(controller, map);
                                $timeout(function() {
                                    // this will be executed after the $digest cycle
                                    scope.inUpdateCycle = false;
                                }, 0);
                            });
                        });

                        // clean up
                        scope.$on('$destroy', function() {
                            if (deregister) {
                                deregister();
                            }
                            map.destroy();
                        });
                    });
                };
            },

            // directive api
            controller: function(/*$scope, $element, */$attrs) {
                // get a reference to the controller
                var self = this;

                // only do this once per directive
                // this deferred will be resolved with the map
                var mapDeferred = $q.defer();

                // add this map to the registry
                if ($attrs.registerAs) {
                    deregister = esriRegistry._register($attrs.registerAs, mapDeferred);
                }

                require(['esri/map', 'esri/arcgis/utils', 'esri/geometry/Extent', 'esri/dijit/Popup'], function(Map, arcgisUtils, Extent, Popup) {
                    // setup our mapOptions based on object hash from attribute string
                    // or from scope object property
                    var mapOptions = self.mapOptions() || {};

                    // construct optional Extent for mapOptions
                    if (mapOptions.hasOwnProperty('extent')) {
                        // construct if parent controller isn't supplying a valid and already constructed Extent
                        // e.g. if the controller or HTML view are only providing JSON
                        if (mapOptions.extent.declaredClass !== 'esri.geometry.Extent') {
                            mapOptions.extent = new Extent(mapOptions.extent);
                        }
                    }

                    // construct optional infoWindow from mapOptions
                    // default to a new Popup dijit for now
                    // mapOptions.infoWindow expects:
                    //  {options: <Object>, srcNodeRef: <Node | String>}
                    if (mapOptions.hasOwnProperty('infoWindow')) {
                        if (mapOptions.infoWindow.hasOwnProperty('srcNodeRef')) {
                            mapOptions.infoWindow = new Popup(mapOptions.infoWindow.options || {}, mapOptions.infoWindow.srcNodeRef);
                        }
                    }

                    // check for 1 way bound properties (basemap)
                    // $scope.basemap takes precedence over $scope.mapOptions.basemap
                    if (self.basemap) {
                        mapOptions.basemap = self.basemap;
                    }

                    // check for 2 way bound properties (center and zoom)
                    // $scope.center takes precedence over $scope.mapOptions.center
                    if (self.center) {
                        if (self.center.lng && self.center.lat) {
                            mapOptions.center = [self.center.lng, self.center.lat];
                        } else {
                            mapOptions.center = self.center;
                        }
                    }

                    // $scope.zoom takes precedence over $scope.mapOptions.zoom
                    if (self.zoom) {
                        mapOptions.zoom = self.zoom;
                    }

                    // initialize map and resolve the deferred
                    if ($attrs.webmapId) {
                        // load map object from web map
                        arcgisUtils.createMap($attrs.webmapId, $attrs.id, {
                            mapOptions: mapOptions
                        }).then(function(response) {
                            // update layer infos for legend
                            self.layerInfos = arcgisUtils.getLegendLayers(response);
                            // add item info to scope
                            self.itemInfo = response.itemInfo;
                            // resolve the promise with the map
                            mapDeferred.resolve(response.map);
                        });
                    } else {
                        // create a new map object
                        var map = new Map($attrs.id, mapOptions);
                        // resolve the promise with the map
                        mapDeferred.resolve(map);
                    }
                });

                // method returns the promise that will be resolved with the map
                this.getMap = function() {
                    return mapDeferred.promise;
                };

                // adds the layer, returns the promise that will be resolved with the result of map.addLayer
                this.addLayer = function(layer, index) {
                    // layer: valid JSAPI layer
                    // index: optional <Number>; likely only used internally by, for example, esriFeatureLayer
                    return this.getMap().then(function(map) {
                        return map.addLayer(layer, index);
                    });
                };

                // support removing layers, e.g. when esriFeatureLayer goes out of scope
                this.removeLayer = function (layer) {
                    return this.getMap().then(function (map) {
                        return map.removeLayer(layer);
                    });
                };

                // array to store layer info, needed for legend
                this.addLayerInfo = function(lyrInfo) {
                    if (!this.layerInfos) {
                        this.layerInfos = [lyrInfo];
                    } else {
                        this.layerInfos.unshift(lyrInfo);
                    }
                };
                this.getLayerInfos = function() {
                    return this.layerInfos;
                };
            }
        };
    });

})(angular);
<|endoftext|>


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

'use strict';

(function (root, factory) {
  if (typeof define === 'function' && define.amd) {
    define('pdfjs/display/text_layer', ['exports', 'pdfjs/shared/util',
      'pdfjs/display/dom_utils', 'pdfjs/display/global'], factory);
  } else if (typeof exports !== 'undefined') {
    factory(exports, require('../shared/util.js'), require('./dom_utils.js'),
      require('./global.js'));
  } else {
    factory((root.pdfjsDisplayTextLayer = {}), root.pdfjsSharedUtil,
      root.pdfjsDisplayDOMUtils, root.pdfjsDisplayGlobal);
  }
}(this, function (exports, sharedUtil, displayDOMUtils, displayGlobal) {

var Util = sharedUtil.Util;
var createPromiseCapability = sharedUtil.createPromiseCapability;
var CustomStyle = displayDOMUtils.CustomStyle;
var PDFJS = displayGlobal.PDFJS;

/**
 * Text layer render parameters.
 *
 * @typedef {Object} TextLayerRenderParameters
 * @property {TextContent} textContent - Text content to render (the object is
 *   returned by the page's getTextContent() method).
 * @property {HTMLElement} container - HTML element that will contain text runs.
 * @property {PDFJS.PageViewport} viewport - The target viewport to properly
 *   layout the text runs.
 * @property {Array} textDivs - (optional) HTML elements that are correspond
 *   the text items of the textContent input. This is output and shall be
 *   initially be set to empty array.
 * @property {number} timeout - (optional) Delay in milliseconds before
 *   rendering of the text  runs occurs.
 */
var renderTextLayer = (function renderTextLayerClosure() {
  var MAX_TEXT_DIVS_TO_RENDER = 100000;

  var NonWhitespaceRegexp = /\S/;

  function isAllWhitespace(str) {
    return !NonWhitespaceRegexp.test(str);
  }

  function appendText(textDivs, viewport, geom, styles) {
    var style = styles[geom.fontName];
    var textDiv = document.createElement('div');
    textDivs.push(textDiv);
    if (isAllWhitespace(geom.str)) {
      textDiv.dataset.isWhitespace = true;
      return;
    }
    var tx = Util.transform(viewport.transform, geom.transform);
    var angle = Math.atan2(tx[1], tx[0]);
    if (style.vertical) {
      angle += Math.PI / 2;
    }
    var fontHeight = Math.sqrt((tx[2] * tx[2]) + (tx[3] * tx[3]));
    var fontAscent = fontHeight;
    if (style.ascent) {
      fontAscent = style.ascent * fontAscent;
    } else if (style.descent) {
      fontAscent = (1 + style.descent) * fontAscent;
    }

    var left;
    var top;
    if (angle === 0) {
      left = tx[4];
      top = tx[5] - fontAscent;
    } else {
      left = tx[4] + (fontAscent * Math.sin(angle));
      top = tx[5] - (fontAscent * Math.cos(angle));
    }
    textDiv.style.left = left + 'px';
    textDiv.style.top = top + 'px';
    textDiv.style.fontSize = fontHeight + 'px';
    textDiv.style.fontFamily = style.fontFamily;

    textDiv.textContent = geom.str;
    // |fontName| is only used by the Font Inspector. This test will succeed
    // when e.g. the Font Inspector is off but the Stepper is on, but it's
    // not worth the effort to do a more accurate test.
    if (PDFJS.pdfBug) {
      textDiv.dataset.fontName = geom.fontName;
    }
    // Storing into dataset will convert number into string.
    if (angle !== 0) {
      textDiv.dataset.angle = angle * (180 / Math.PI);
    }
    // We don't bother scaling single-char text divs, because it has very
    // little effect on text highlighting. This makes scrolling on docs with
    // lots of such divs a lot faster.
    if (geom.str.length > 1) {
      if (style.vertical) {
        textDiv.dataset.canvasWidth = geom.height * viewport.scale;
      } else {
        textDiv.dataset.canvasWidth = geom.width * viewport.scale;
      }
    }
  }

  function render(task) {
    if (task._canceled) {
      return;
    }
    var textLayerFrag = task._container;
    var textDivs = task._textDivs;
    var capability = task._capability;
    var textDivsLength = textDivs.length;

    // No point in rendering many divs as it would make the browser
    // unusable even after the divs are rendered.
    if (textDivsLength > MAX_TEXT_DIVS_TO_RENDER) {
      capability.resolve();
      return;
    }

    var canvas = document.createElement('canvas');
//#if MOZCENTRAL || FIREFOX || GENERIC
    canvas.mozOpaque = true;
//#endif
    var ctx = canvas.getContext('2d', {alpha: false});

    var lastFontSize;
    var lastFontFamily;
    for (var i = 0; i < textDivsLength; i++) {
      var textDiv = textDivs[i];
      if (textDiv.dataset.isWhitespace !== undefined) {
        continue;
      }

      var fontSize = textDiv.style.fontSize;
      var fontFamily = textDiv.style.fontFamily;

      // Only build font string and set to context if different from last.
      if (fontSize !== lastFontSize || fontFamily !== lastFontFamily) {
        ctx.font = fontSize + ' ' + fontFamily;
        lastFontSize = fontSize;
        lastFontFamily = fontFamily;
      }

      var width = ctx.measureText(textDiv.textContent).width;
      if (width > 0) {
        textLayerFrag.appendChild(textDiv);
        var transform;
        if (textDiv.dataset.canvasWidth !== undefined) {
          // Dataset values come of type string.
          var textScale = textDiv.dataset.canvasWidth / width;
          transform = 'scaleX(' + textScale + ')';
        } else {
          transform = '';
        }
        var rotation = textDiv.dataset.angle;
        if (rotation) {
          transform = 'rotate(' + rotation + 'deg) ' + transform;
        }
        if (transform) {
          CustomStyle.setProp('transform' , textDiv, transform);
        }
      }
    }
    capability.resolve();
  }

  /**
   * Text layer rendering task.
   *
   * @param {TextContent} textContent
   * @param {HTMLElement} container
   * @param {PDFJS.PageViewport} viewport
   * @param {Array} textDivs
   * @private
   */
  function TextLayerRenderTask(textContent, container, viewport, textDivs) {
    this._textContent = textContent;
    this._container = container;
    this._viewport = viewport;
    textDivs = textDivs || [];
    this._textDivs = textDivs;
    this._canceled = false;
    this._capability = createPromiseCapability();
    this._renderTimer = null;
  }
  TextLayerRenderTask.prototype = {
    get promise() {
      return this._capability.promise;
    },

    cancel: function TextLayer_cancel() {
      this._canceled = true;
      if (this._renderTimer !== null) {
        clearTimeout(this._renderTimer);
        this._renderTimer = null;
      }
      this._capability.reject('canceled');
    },

    _render: function TextLayer_render(timeout) {
      var textItems = this._textContent.items;
      var styles = this._textContent.styles;
      var textDivs = this._textDivs;
      var viewport = this._viewport;
      for (var i = 0, len = textItems.length; i < len; i++) {
        appendText(textDivs, viewport, textItems[i], styles);
      }

      if (!timeout) { // Render right away
        render(this);
      } else { // Schedule
        var self = this;
        this._renderTimer = setTimeout(function() {
          render(self);
          self._renderTimer = null;
        }, timeout);
      }
    }
  };

  /**
   * Starts rendering of the text layer.
   *
   * @param {TextLayerRenderParameters} renderParameters
   * @returns {TextLayerRenderTask}
   */
  function renderTextLayer(renderParameters) {
    var task = new TextLayerRenderTask(renderParameters.textContent,
                                       renderParameters.container,
                                       renderParameters.viewport,
                                       renderParameters.textDivs);
    task._render(renderParameters.timeout);
    return task;
  }

  return renderTextLayer;
})();

PDFJS.renderTextLayer = renderTextLayer;

exports.renderTextLayer = renderTextLayer;
}));
<commit_msg>Add element to text layer even if width === 0<commit_after>/* Copyright 2015 Mozilla Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

'use strict';

(function (root, factory) {
  if (typeof define === 'function' && define.amd) {
    define('pdfjs/display/text_layer', ['exports', 'pdfjs/shared/util',
      'pdfjs/display/dom_utils', 'pdfjs/display/global'], factory);
  } else if (typeof exports !== 'undefined') {
    factory(exports, require('../shared/util.js'), require('./dom_utils.js'),
      require('./global.js'));
  } else {
    factory((root.pdfjsDisplayTextLayer = {}), root.pdfjsSharedUtil,
      root.pdfjsDisplayDOMUtils, root.pdfjsDisplayGlobal);
  }
}(this, function (exports, sharedUtil, displayDOMUtils, displayGlobal) {

var Util = sharedUtil.Util;
var createPromiseCapability = sharedUtil.createPromiseCapability;
var CustomStyle = displayDOMUtils.CustomStyle;
var PDFJS = displayGlobal.PDFJS;

/**
 * Text layer render parameters.
 *
 * @typedef {Object} TextLayerRenderParameters
 * @property {TextContent} textContent - Text content to render (the object is
 *   returned by the page's getTextContent() method).
 * @property {HTMLElement} container - HTML element that will contain text runs.
 * @property {PDFJS.PageViewport} viewport - The target viewport to properly
 *   layout the text runs.
 * @property {Array} textDivs - (optional) HTML elements that are correspond
 *   the text items of the textContent input. This is output and shall be
 *   initially be set to empty array.
 * @property {number} timeout - (optional) Delay in milliseconds before
 *   rendering of the text  runs occurs.
 */
var renderTextLayer = (function renderTextLayerClosure() {
  var MAX_TEXT_DIVS_TO_RENDER = 100000;

  var NonWhitespaceRegexp = /\S/;

  function isAllWhitespace(str) {
    return !NonWhitespaceRegexp.test(str);
  }

  function appendText(textDivs, viewport, geom, styles) {
    var style = styles[geom.fontName];
    var textDiv = document.createElement('div');
    textDivs.push(textDiv);
    if (isAllWhitespace(geom.str)) {
      textDiv.dataset.isWhitespace = true;
      return;
    }
    var tx = Util.transform(viewport.transform, geom.transform);
    var angle = Math.atan2(tx[1], tx[0]);
    if (style.vertical) {
      angle += Math.PI / 2;
    }
    var fontHeight = Math.sqrt((tx[2] * tx[2]) + (tx[3] * tx[3]));
    var fontAscent = fontHeight;
    if (style.ascent) {
      fontAscent = style.ascent * fontAscent;
    } else if (style.descent) {
      fontAscent = (1 + style.descent) * fontAscent;
    }

    var left;
    var top;
    if (angle === 0) {
      left = tx[4];
      top = tx[5] - fontAscent;
    } else {
      left = tx[4] + (fontAscent * Math.sin(angle));
      top = tx[5] - (fontAscent * Math.cos(angle));
    }
    textDiv.style.left = left + 'px';
    textDiv.style.top = top + 'px';
    textDiv.style.fontSize = fontHeight + 'px';
    textDiv.style.fontFamily = style.fontFamily;

    textDiv.textContent = geom.str;
    // |fontName| is only used by the Font Inspector. This test will succeed
    // when e.g. the Font Inspector is off but the Stepper is on, but it's
    // not worth the effort to do a more accurate test.
    if (PDFJS.pdfBug) {
      textDiv.dataset.fontName = geom.fontName;
    }
    // Storing into dataset will convert number into string.
    if (angle !== 0) {
      textDiv.dataset.angle = angle * (180 / Math.PI);
    }
    // We don't bother scaling single-char text divs, because it has very
    // little effect on text highlighting. This makes scrolling on docs with
    // lots of such divs a lot faster.
    if (geom.str.length > 1) {
      if (style.vertical) {
        textDiv.dataset.canvasWidth = geom.height * viewport.scale;
      } else {
        textDiv.dataset.canvasWidth = geom.width * viewport.scale;
      }
    }
  }

  function render(task) {
    if (task._canceled) {
      return;
    }
    var textLayerFrag = task._container;
    var textDivs = task._textDivs;
    var capability = task._capability;
    var textDivsLength = textDivs.length;

    // No point in rendering many divs as it would make the browser
    // unusable even after the divs are rendered.
    if (textDivsLength > MAX_TEXT_DIVS_TO_RENDER) {
      capability.resolve();
      return;
    }

    var canvas = document.createElement('canvas');
//#if MOZCENTRAL || FIREFOX || GENERIC
    canvas.mozOpaque = true;
//#endif
    var ctx = canvas.getContext('2d', {alpha: false});

    var lastFontSize;
    var lastFontFamily;
    for (var i = 0; i < textDivsLength; i++) {
      var textDiv = textDivs[i];
      if (textDiv.dataset.isWhitespace !== undefined) {
        continue;
      }

      var fontSize = textDiv.style.fontSize;
      var fontFamily = textDiv.style.fontFamily;

      // Only build font string and set to context if different from last.
      if (fontSize !== lastFontSize || fontFamily !== lastFontFamily) {
        ctx.font = fontSize + ' ' + fontFamily;
        lastFontSize = fontSize;
        lastFontFamily = fontFamily;
      }

      var width = ctx.measureText(textDiv.textContent).width;
      textLayerFrag.appendChild(textDiv);
      var transform;
      if (textDiv.dataset.canvasWidth !== undefined && width > 0) {
        // Dataset values come of type string.
        var textScale = textDiv.dataset.canvasWidth / width;
        transform = 'scaleX(' + textScale + ')';
      } else {
        transform = '';
      }
      var rotation = textDiv.dataset.angle;
      if (rotation) {
        transform = 'rotate(' + rotation + 'deg) ' + transform;
      }
      if (transform) {
        CustomStyle.setProp('transform' , textDiv, transform);
      }
    }
    capability.resolve();
  }

  /**
   * Text layer rendering task.
   *
   * @param {TextContent} textContent
   * @param {HTMLElement} container
   * @param {PDFJS.PageViewport} viewport
   * @param {Array} textDivs
   * @private
   */
  function TextLayerRenderTask(textContent, container, viewport, textDivs) {
    this._textContent = textContent;
    this._container = container;
    this._viewport = viewport;
    textDivs = textDivs || [];
    this._textDivs = textDivs;
    this._canceled = false;
    this._capability = createPromiseCapability();
    this._renderTimer = null;
  }
  TextLayerRenderTask.prototype = {
    get promise() {
      return this._capability.promise;
    },

    cancel: function TextLayer_cancel() {
      this._canceled = true;
      if (this._renderTimer !== null) {
        clearTimeout(this._renderTimer);
        this._renderTimer = null;
      }
      this._capability.reject('canceled');
    },

    _render: function TextLayer_render(timeout) {
      var textItems = this._textContent.items;
      var styles = this._textContent.styles;
      var textDivs = this._textDivs;
      var viewport = this._viewport;
      for (var i = 0, len = textItems.length; i < len; i++) {
        appendText(textDivs, viewport, textItems[i], styles);
      }

      if (!timeout) { // Render right away
        render(this);
      } else { // Schedule
        var self = this;
        this._renderTimer = setTimeout(function() {
          render(self);
          self._renderTimer = null;
        }, timeout);
      }
    }
  };

  /**
   * Starts rendering of the text layer.
   *
   * @param {TextLayerRenderParameters} renderParameters
   * @returns {TextLayerRenderTask}
   */
  function renderTextLayer(renderParameters) {
    var task = new TextLayerRenderTask(renderParameters.textContent,
                                       renderParameters.container,
                                       renderParameters.viewport,
                                       renderParameters.textDivs);
    task._render(renderParameters.timeout);
    return task;
  }

  return renderTextLayer;
})();

PDFJS.renderTextLayer = renderTextLayer;

exports.renderTextLayer = renderTextLayer;
}));
<|endoftext|>


<commit_before>/**
 *  Thin wrapper for XMLHttpRequest using a
 *  callback style.
 */
function request(opts, cb) {
  opts = opts || {};
  opts.method = opts.method || 'GET';
  opts.headers = opts.headers || {};

  var req
    , z;

  /* jshint ignore:start */
  req = new XMLHttpRequest();
  /* jshint ignore:end */

  /**
   *  Parse response headers into an object.
   */
  function parse(headers) {
    var output = {}, i, p, k, v;
    headers = headers || '';
    headers = headers.replace('\r', '');
    headers = headers.split('\n');
    for(i = 0;i < headers.length;i++) {
      p = headers[i].indexOf(':');
      k = headers[i].substr(0, p);
      v = headers[i].substr(p + 1);
      if(k && v) {
        k = k.replace(/^\s+/, '').replace(/\s+$/, '');
        v = v.replace(/^\s+/, '').replace(/\s+$/, '');
        output[k.toLowerCase()] = v;
      }
    }
    return output;
  }

  function done() {
    req.onload = null;
    req.onerror = null;
    req.ontimeout = null;
    req.onprogress = null;
    cb.apply(null, arguments);
  }

  if(opts.fields) {
    for(z in opts.fields) {
      req[z] = opts.fields[z];
    }
  }

  opts.headers['X-Requested-With'] = 'XMLHttpRequest';

  if(opts.json) {
    opts.headers['Content-Type'] = 'application/json';
    if(opts.body !== undefined) {
      opts.body = JSON.stringify(opts.body);
    }
  }

  var url = opts.url;

  if(opts.qs) {
    var keys = Object.keys(opts.qs)
      , qs = '?';
    keys.forEach(function(key) {
      qs += encodeURIComponent(key) + '&' + encodeURIComponent(opts.qs[key]);
    })
  }

  req.open(opts.method, url);

  // apply custom request headers
  for(z in opts.headers) {
    req.setRequestHeader(z, opts.headers[z]);
  }

  if(opts.mime && typeof(req.overrideMimeType) === 'function') {
    req.overrideMimeType(opts.mime);
  }

  req.onreadystatechange = function() {
    if(this.readyState === 4) {
      var res = this.responseText
        , info = {
            headers: parse(this.getAllResponseHeaders()), status: this.status};
      info.body = res;
      if(res && opts.json && (this.status === 200 || this.status === 201)) {
        try {
          info.body = JSON.parse(res);
        }catch(e) {
          return done(
            e,
            info,
            this);
        }
      }
      done(
        null,
        info,
        this);
    }
  }

  req.onerror = opts.error || function onError(err) {
    done(err);
  }

  req.onload = opts.load;
  req.ontimeout = opts.timeout;
  req.onprogress = opts.progress;

  req.send(opts.body);
}

module.exports = function() {
  this.air.request = request;
}
<commit_msg>Append query string to URL (request).<commit_after>/**
 *  Thin wrapper for XMLHttpRequest using a
 *  callback style.
 */
function request(opts, cb) {
  opts = opts || {};
  opts.method = opts.method || 'GET';
  opts.headers = opts.headers || {};

  var req
    , z;

  /* jshint ignore:start */
  req = new XMLHttpRequest();
  /* jshint ignore:end */

  /**
   *  Parse response headers into an object.
   */
  function parse(headers) {
    var output = {}, i, p, k, v;
    headers = headers || '';
    headers = headers.replace('\r', '');
    headers = headers.split('\n');
    for(i = 0;i < headers.length;i++) {
      p = headers[i].indexOf(':');
      k = headers[i].substr(0, p);
      v = headers[i].substr(p + 1);
      if(k && v) {
        k = k.replace(/^\s+/, '').replace(/\s+$/, '');
        v = v.replace(/^\s+/, '').replace(/\s+$/, '');
        output[k.toLowerCase()] = v;
      }
    }
    return output;
  }

  function done() {
    req.onload = null;
    req.onerror = null;
    req.ontimeout = null;
    req.onprogress = null;
    cb.apply(null, arguments);
  }

  if(opts.fields) {
    for(z in opts.fields) {
      req[z] = opts.fields[z];
    }
  }

  opts.headers['X-Requested-With'] = 'XMLHttpRequest';

  if(opts.json) {
    opts.headers['Content-Type'] = 'application/json';
    if(opts.body !== undefined) {
      opts.body = JSON.stringify(opts.body);
    }
  }

  var url = opts.url;

  if(opts.qs) {
    var keys = Object.keys(opts.qs)
      , qs = '?';
    keys.forEach(function(key, index) {
      if(index) {
        qs += '&';
      }
      qs += encodeURIComponent(key) + '=' + encodeURIComponent(opts.qs[key]);
    })

    url += qs;
  }

  req.open(opts.method, url);

  // apply custom request headers
  for(z in opts.headers) {
    req.setRequestHeader(z, opts.headers[z]);
  }

  if(opts.mime && typeof(req.overrideMimeType) === 'function') {
    req.overrideMimeType(opts.mime);
  }

  req.onreadystatechange = function() {
    if(this.readyState === 4) {
      var res = this.responseText
        , info = {
            headers: parse(this.getAllResponseHeaders()), status: this.status};
      info.body = res;
      if(res && opts.json && (this.status === 200 || this.status === 201)) {
        try {
          info.body = JSON.parse(res);
        }catch(e) {
          return done(
            e,
            info,
            this);
        }
      }
      done(
        null,
        info,
        this);
    }
  }

  req.onerror = opts.error || function onError(err) {
    done(err);
  }

  req.onload = opts.load;
  req.ontimeout = opts.timeout;
  req.onprogress = opts.progress;

  req.send(opts.body);
}

module.exports = function() {
  this.air.request = request;
}
<|endoftext|>


<commit_before>import Promise from 'yaku'

export default class Channeld {

  constructor (sourceId) {
    this.sourceId = sourceId
    this.messageID = 0
    this.messageHandlers = {}
    this.responseHandlers = {}

    window.addEventListener('message', (event) => {
      this._handleMessage(event.data)
    })
  }

  call (method, ...params) {
    var messageID = this._send(method, params)
    return new Promise((resolve, reject) => {
      this.responseHandlers[messageID] = {resolve, reject}
    })
  }

  send (method, ...params) {
    this._send(method, params)
  }

  addHandler (method, handler) {
    if (!(method in this.messageHandlers)) {
      this.messageHandlers[method] = []
    }
    this.messageHandlers[method].push(handler)
  }

  _handleMessage (message) {
    if (message.method) {
      // console.log('received message', message)
      let {method, params} = message
      let handlers = this.messageHandlers[method] || []
      handlers.forEach((handler) => {
        handler(...params)
      })
    } else {
      let {id, result, error} = message
      let handler = this.responseHandlers[id]

      if (!handler) {
        return
      }

      if (result) {
        handler.resolve(result)
      } else if (error) {
        handler.reject(error)
      }

      delete this.responseHandlers[id]
    }
  }

  _send (method, params) {
    var messageID = this.messageID++

    window.parent.postMessage({
      source: this.sourceId,
      id: messageID,
      method,
      params
    }, '*')

    return messageID
  }

}
<commit_msg>fix(api): Fix typo in API class<commit_after>import Promise from 'yaku'

export default class Channel {

  constructor (sourceId) {
    this.sourceId = sourceId
    this.messageID = 0
    this.messageHandlers = {}
    this.responseHandlers = {}

    window.addEventListener('message', (event) => {
      this._handleMessage(event.data)
    })
  }

  call (method, ...params) {
    var messageID = this._send(method, params)
    return new Promise((resolve, reject) => {
      this.responseHandlers[messageID] = {resolve, reject}
    })
  }

  send (method, ...params) {
    this._send(method, params)
  }

  addHandler (method, handler) {
    if (!(method in this.messageHandlers)) {
      this.messageHandlers[method] = []
    }
    this.messageHandlers[method].push(handler)
  }

  _handleMessage (message) {
    if (message.method) {
      // console.log('received message', message)
      let {method, params} = message
      let handlers = this.messageHandlers[method] || []
      handlers.forEach((handler) => {
        handler(...params)
      })
    } else {
      let {id, result, error} = message
      let handler = this.responseHandlers[id]

      if (!handler) {
        return
      }

      if (result) {
        handler.resolve(result)
      } else if (error) {
        handler.reject(error)
      }

      delete this.responseHandlers[id]
    }
  }

  _send (method, params) {
    var messageID = this.messageID++

    window.parent.postMessage({
      source: this.sourceId,
      id: messageID,
      method,
      params
    }, '*')

    return messageID
  }

}
<|endoftext|>


<commit_before>/**
 * Module dependencies
 */

var debug = require('debug')('dad:application');
var Emitter = require('events').EventEmitter;
var assert = require('assert');

var transaction = require('./transaction');
var persistance = require('./persistance');
var validation = require('./validation');

/**
 * Expose 'Store'.
 */

exports = module.exports = Store;

/**
 * 'Store' prototype
 */

var store = Store.prototype;

/**
 * Create a new 'Store'.
 *
 * @param {String} name
 * @param {Object} opts
 * @return {Store}
 * @api public
 */

function Store(name) {
  if (!(this instanceof Store)) return new Store(name);
  this._name = name || '';
  this._transactions = [];
  this._adapters = [];
  this._cidCount = 0;
  this._schema = {};
  this._store = {};
  return this;
};

/**
 * Inherit from `Emitter.prototype`.
 */

Store.prototype.__proto__ = Emitter.prototype;

/**
 * Define the model schema. This
 * defines how our data should
 * look like.
 *
 * @params {String} attribute
 * @params {Object[]} meta
 * @return {Store}
 * @api public
 */

Object.defineProperty(store, 'schema', {
  set: function(schema) {
    this._schema = schema;
  }
});

/**
 * Delegates.
 */

store.update = transaction.update;
store.remove = transaction.remove;
store.add = transaction.add;
store.get = transaction.get;

store.baseUrl = persistance.baseUrl;
store.adapter = persistance.adapter;
store.fetch = persistance.fetch;
store.push = persistance.push;

store.allAccountedFor = validation.allAccountedFor;
store.validate = validation.validate;
<commit_msg>application: add adapters definition<commit_after>/**
 * Module dependencies
 */

var debug = require('debug')('dad:application');
var Emitter = require('events').EventEmitter;
var assert = require('assert');

var transaction = require('./transaction');
var persistance = require('./persistance');
var validation = require('./validation');

/**
 * Expose 'Store'.
 */

exports = module.exports = Store;

/**
 * 'Store' prototype
 */

var store = Store.prototype;

/**
 * Create a new 'Store'.
 *
 * @param {String} name
 * @return {Store}
 * @api public
 */

function Store(name) {
  if (!(this instanceof Store)) return new Store(name);
  this._name = name || '';
  this._transactions = [];
  this._adapters = [];
  this._cidCount = 0;
  this._schema = {};
  this._store = {};
  return this;
};

/**
 * Inherit from `Emitter.prototype`.
 */

Store.prototype.__proto__ = Emitter.prototype;

/**
 * Define the model schema. This
 * defines how our data should
 * look like.
 *
 * @params {Object[]} schema
 * @api public
 */

Object.defineProperty(store, 'schema', {
  set: function(schema) {
    this._schema = schema;
  }
});

/**
 * Define the adapters on the store.
 *
 * @param {Function[]} adapters
 * @api public
 */

Object.defineProperty(store, 'adapters', {
  set: function(adapters) {
    this._adapters = adapters;
  }
});

/**
 * Delegates.
 */

store.update = transaction.update;
store.remove = transaction.remove;
store.add = transaction.add;
store.get = transaction.get;

store.baseUrl = persistance.baseUrl;
store.adapter = persistance.adapter;
store.fetch = persistance.fetch;
store.push = persistance.push;

store.allAccountedFor = validation.allAccountedFor;
store.validate = validation.validate;
<|endoftext|>


<commit_before>/**
 * @fileOverview Uploader上传类
 */
define([
    './base',
    './mediator'
], function( Base, Mediator ) {

    var $ = Base.$;

    /**
     * 上传入口类。
     * @class Uploader
     * @constructor 构造器，用来初始化一个Uploader实例。
     * @grammar new Uploader( opts ) => Uploader
     */
    function Uploader( opts ) {
        this.options = $.extend( true, {}, Uploader.options, opts );
        this._init( this.options );
    }

    // default Options
    // widgets中有相应扩展
    Uploader.options = {};
    Mediator.installTo( Uploader.prototype );

    // 批量添加纯命令式方法。
    $.each({
        upload: 'start-upload',
        stop: 'stop-upload',
        getFile: 'get-file',
        getFiles: 'get-files',
        // addFile: 'add-file',
        // addFiles: 'add-file',
        removeFile: 'remove-file',
        skipFile: 'skip-file',
        retry: 'retry',
        isInProgress: 'is-in-progress',
        makeThumb: 'make-thumb',
        getDimension: 'get-dimension',
        addButton: 'add-btn',
        getRuntimeType: 'get-runtime-type',
        refresh: 'refresh',
        disable: 'disable',
        enable: 'enable'
    }, function( fn, command ) {
        Uploader.prototype[ fn ] = function() {
            return this.request( command, arguments );
        };
    });

    $.extend( Uploader.prototype, {
        state: 'pending',

        _init: function( opts ) {
            var me = this;

            me.request( 'init', opts, function() {
                me.state = 'ready';
                me.trigger('ready');
            });
        },

        /**
         * 获取或者设置Uploader配置项。
         * @method option
         * @grammar option( key ) => *
         * @grammar option( key, val ) => self
         * @example
         *
         * // 初始状态图片上传前不会压缩
         * var uploader = new WebUploader.Uploader({
         *     resize: null;
         * });
         *
         * // 修改后图片上传前，尝试将图片压缩到1600 * 1600
         * uploader.options( 'resize', {
         *     width: 1600,
         *     height: 1600
         * });
         */
        option: function( key, val ) {
            var opts = this.options;

            // setter
            if ( arguments.length > 1 ) {

                if ( $.isPlainObject( val ) &&
                        $.isPlainObject( opts[ key ] ) ) {
                    $.extend( opts[ key ], val );
                } else {
                    opts[ key ] = val;
                }

            } else {    // getter
                return key ? opts[ key ] : opts;
            }
        },

        /**
         * 获取文件统计信息。返回一个包含一下信息的对象。
         * * `successNum` 上传成功的文件数
         * * `uploadFailNum` 上传失败的文件数
         * * `cancelNum` 被删除的文件数
         * * `invalidNum` 无效的文件数
         * * `queueNum` 还在队列中的文件数
         * @method getStats
         * @grammar getStats() => Object
         */
        getStats: function() {
            // return this._mgr.getStats.apply( this._mgr, arguments );
            var stats = this.request('get-stats');

            return {
                successNum: stats.numOfSuccess,

                // who care?
                // queueFailNum: 0,
                cancelNum: stats.numOfCancel,
                invalidNum: stats.numOfInvalid,
                uploadFailNum: stats.numOfUploadFailed,
                queueNum: stats.numOfQueue
            };
        },

        // 需要重写此方法来来支持opts.onEvent和instance.onEvent的处理器
        trigger: function( type/*, args...*/ ) {
            var args = [].slice.call( arguments, 1 ),
                opts = this.options,
                name = 'on' + type.substring( 0, 1 ).toUpperCase() +
                    type.substring( 1 );

            if ( Mediator.trigger.apply( this, arguments ) === false ) {
                return false;
            }

            if ( $.isFunction( opts[ name ] ) &&
                    opts[ name ].apply( this, args ) === false ) {
                return false;
            }

            if ( $.isFunction( this[ name ] ) &&
                    this[ name ].apply( this, args ) === false ) {
                return false;
            }

            return true;
        },

        /**
         * @method request
         * @grammar request( command, args ) => * | Promise
         * @grammar request( command, args, callback ) => Promise
         */
        request: Base.noop,

        reset: function() {
            // @todo
        }
    });

    Base.create = function( opts ) {
        return new Uploader( opts );
    };

    // 暴露Uploader，可以通过它来扩展业务逻辑。
    Base.Uploader = Uploader;

    return Uploader;
});<commit_msg>dont know<commit_after>/**
 * @fileOverview Uploader上传类
 */
define([
    './base',
    './mediator'
], function( Base, Mediator ) {

    var $ = Base.$;

    /**
     * 上传入口类。
     * @class Uploader
     * @constructor 构造器，用来初始化一个Uploader实例。
     * @grammar new Uploader( opts ) => Uploader
     */
    function Uploader( opts ) {
        this.options = $.extend( true, {}, Uploader.options, opts );
        this._init( this.options );
    }

    // default Options
    // widgets中有相应扩展
    Uploader.options = {};
    Mediator.installTo( Uploader.prototype );

    // 批量添加纯命令式方法。
    $.each({
        upload: 'start-upload',
        stop: 'stop-upload',
        getFile: 'get-file',
        getFiles: 'get-files',
        // addFile: 'add-file',
        // addFiles: 'add-file',
        removeFile: 'remove-file',
        skipFile: 'skip-file',
        retry: 'retry',
        isInProgress: 'is-in-progress',
        makeThumb: 'make-thumb',
        getDimension: 'get-dimension',
        addButton: 'add-btn',
        getRuntimeType: 'get-runtime-type',
        refresh: 'refresh',
        disable: 'disable',
        enable: 'enable'
    }, function( fn, command ) {
        Uploader.prototype[ fn ] = function() {
            return this.request( command, arguments );
        };
    });

    $.extend( Uploader.prototype, {
        state: 'pending',

        _init: function( opts ) {
            var me = this;

            me.request( 'init', opts, function() {
                me.state = 'ready';
                me.trigger('ready');
            });
        },

        /**
         * 获取或者设置Uploader配置项。
         * @method option
         * @grammar option( key ) => *
         * @grammar option( key, val ) => self
         * @example
         *
         * // 初始状态图片上传前不会压缩
         * var uploader = new WebUploader.Uploader({
         *     resize: null;
         * });
         *
         * // 修改后图片上传前，尝试将图片压缩到1600 * 1600
         * uploader.options( 'resize', {
         *     width: 1600,
         *     height: 1600
         * });
         */
        option: function( key, val ) {
            var opts = this.options;

            // setter
            if ( arguments.length > 1 ) {

                if ( $.isPlainObject( val ) &&
                        $.isPlainObject( opts[ key ] ) ) {
                    $.extend( opts[ key ], val );
                } else {
                    opts[ key ] = val;
                }

            } else {    // getter
                return key ? opts[ key ] : opts;
            }
        },

        /**
         * 获取文件统计信息。返回一个包含一下信息的对象。
         * * `successNum` 上传成功的文件数
         * * `uploadFailNum` 上传失败的文件数
         * * `cancelNum` 被删除的文件数
         * * `invalidNum` 无效的文件数
         * * `queueNum` 还在队列中的文件数
         * @method getStats
         * @grammar getStats() => Object
         */
        getStats: function() {
            // return this._mgr.getStats.apply( this._mgr, arguments );
            var stats = this.request('get-stats');

            return {
                successNum: stats.numOfSuccess,

                // who care?
                // queueFailNum: 0,
                cancelNum: stats.numOfCancel,
                invalidNum: stats.numOfInvalid,
                uploadFailNum: stats.numOfUploadFailed,
                queueNum: stats.numOfQueue
            };
        },

        // 需要重写此方法来来支持opts.onEvent和instance.onEvent的处理器
        trigger: function( type/*, args...*/ ) {
            var args = [].slice.call( arguments, 1 ),
                opts = this.options,
                name = 'on' + type.substring( 0, 1 ).toUpperCase() +
                    type.substring( 1 );

            if ( Mediator.trigger.apply( this, arguments ) === false ) {
                return false;
            }

            if ( $.isFunction( opts[ name ] ) &&
                    opts[ name ].apply( this, args ) === false ) {
                return false;
            }

            if ( $.isFunction( this[ name ] ) &&
                    this[ name ].apply( this, args ) === false ) {
                return false;
            }

            return true;
        },

        /**
         * @method request
         * @grammar request( command, args ) => * | Promise
         * @grammar request( command, args, callback ) => Promise
         */
        request: Base.noop,

        reset: function() {
            // @todo
        }
    });

    /**
     * 创建Uploader实例，等同于new Uploader( opts );
     * @method create
     * @class Base
     * @static
     * @grammar Base.create( opts ) => Uploader
     */
    Base.create = function( opts ) {
        return new Uploader( opts );
    };

    // 暴露Uploader，可以通过它来扩展业务逻辑。
    Base.Uploader = Uploader;

    return Uploader;
});<|endoftext|>


<commit_before>var util = require('util'),
    _ = require('underscore'),
    iconv = require('iconv'),
    error = require('../error'),
    memoizeAsyncAccessor = require('../memoizeAsyncAccessor'),
    Base = require('./Base');

// Superclass for text-based assets that need support for different encodings.
function Text(config) {
    Base.call(this, config);
}

util.inherits(Text, Base);

_.extend(Text.prototype, {
    isText: true,

    defaultEncoding: 'utf-8', // Change to null in subclass for images etc.

    getOriginalEncoding: function (cb) {
        var that = this;
        process.nextTick(function () {
            cb(null, that.originalEncoding || that.defaultEncoding);
        });
    },

    getTargetEncoding: function (cb) {
        if (this.targetEncoding) {
            var targetEncoding = this.targetEncoding;
            process.nextTick(function () {
                cb(targetEncoding);
            });
        } else {
            this.getOriginalEncoding(cb);
        }
    },

    setTargetEncoding: function (encoding) {
        this.targetEncoding = encoding;
    },

    // Only applies to text-based assets (maybe create assets.Text subclass?)
    getDecodedSrc: memoizeAsyncAccessor('decodedSrc', function (cb) {
        var that = this;
        that.getRawSrc(error.passToFunction(cb, function (rawSrc) {
            that.getOriginalEncoding(error.passToFunction(cb, function (originalEncoding) {
                if (/^utf-?8$/i.test(originalEncoding)) {
                    cb(null, rawSrc.toString('utf-8'));
                } else {
                    cb(null, new iconv.Iconv(originalEncoding, 'utf-8').convert(rawSrc).toString('utf-8'));
                }
            }));
        }));
    }),

    // Implement a getText method in subclass if an intermediate representation/parse tree is used for manipulation.
    getSerializedSrc: memoizeAsyncAccessor('serializedSrc', function (cb) {
        var that = this;
        that.getOriginalEncoding(function (err, originalEncoding) {
            if (err && !that.decodedSrc) {
                return cb(err);
            }
            // Else assume it's an inline asset or that it never existed in "raw form"
            if ('decodedSrc' in that || that.isDirty || !that.hasRawSrc()) {
                // TODO: Take manipulatable non-text assets into account somehow.
                that.getText(error.passToFunction(cb, function (text) {
                    var targetEncoding = that.targetEncoding || originalEncoding || that.defaultEncoding;
                    if (/^utf-?8$/i.test(targetEncoding)) {
                        cb(null, new Buffer(text));
                    } else {
                        cb(null, new iconv.Iconv('utf-8', targetEncoding).convert(text));
                    }
                }));
            } else {
                that.getRawSrc(error.passToFunction(cb, function (rawSrc) {
                    if (!that.targetEncoding || that.targetEncoding === encoding) {
                        cb(null, rawSrc);
                    } else {
                        cb(null, new iconv.Iconv(originalEncoding, that.targetEncoding).convert(rawSrc));
                    }
                }));
            }
        });
    }),

    // More efficient than Base._clone for text-based assets:
    _clone: function (cb) {
        var that = this;
        that[that.getText ? 'getText' : 'getDecodedSrc'](error.passToFunction(cb, function (text) {
            cb(null, new that.constructor({
                targetEncoding: that.targetEncoding,
                decodedSrc: text
            }));
        }));
    }
});

module.exports = Text;
<commit_msg>assets.Text.getSerializedSrc bugfix: Also call getText if there's a 'parseTree' property.<commit_after>var util = require('util'),
    _ = require('underscore'),
    iconv = require('iconv'),
    error = require('../error'),
    memoizeAsyncAccessor = require('../memoizeAsyncAccessor'),
    Base = require('./Base');

// Superclass for text-based assets that need support for different encodings.
function Text(config) {
    Base.call(this, config);
}

util.inherits(Text, Base);

_.extend(Text.prototype, {
    isText: true,

    defaultEncoding: 'utf-8', // Change to null in subclass for images etc.

    getOriginalEncoding: function (cb) {
        var that = this;
        process.nextTick(function () {
            cb(null, that.originalEncoding || that.defaultEncoding);
        });
    },

    getTargetEncoding: function (cb) {
        if (this.targetEncoding) {
            var targetEncoding = this.targetEncoding;
            process.nextTick(function () {
                cb(targetEncoding);
            });
        } else {
            this.getOriginalEncoding(cb);
        }
    },

    setTargetEncoding: function (encoding) {
        this.targetEncoding = encoding;
    },

    // Only applies to text-based assets (maybe create assets.Text subclass?)
    getDecodedSrc: memoizeAsyncAccessor('decodedSrc', function (cb) {
        var that = this;
        that.getRawSrc(error.passToFunction(cb, function (rawSrc) {
            that.getOriginalEncoding(error.passToFunction(cb, function (originalEncoding) {
                if (/^utf-?8$/i.test(originalEncoding)) {
                    cb(null, rawSrc.toString('utf-8'));
                } else {
                    cb(null, new iconv.Iconv(originalEncoding, 'utf-8').convert(rawSrc).toString('utf-8'));
                }
            }));
        }));
    }),

    // Implement a getText method in subclass if an intermediate representation/parse tree is used for manipulation.
    getSerializedSrc: memoizeAsyncAccessor('serializedSrc', function (cb) {
        var that = this;
        that.getOriginalEncoding(function (err, originalEncoding) {
            if (err && !that.decodedSrc) {
                return cb(err);
            }
            // Else assume it's an inline asset or that it never existed in "raw form"
            if ('decodedSrc' in that || 'parseTree' in that || that.isDirty || !that.hasRawSrc()) {
                // TODO: Take manipulatable non-text assets into account somehow.
                that.getText(error.passToFunction(cb, function (text) {
                    var targetEncoding = that.targetEncoding || originalEncoding || that.defaultEncoding;
                    if (/^utf-?8$/i.test(targetEncoding)) {
                        cb(null, new Buffer(text));
                    } else {
                        cb(null, new iconv.Iconv('utf-8', targetEncoding).convert(text));
                    }
                }));
            } else {
                that.getRawSrc(error.passToFunction(cb, function (rawSrc) {
                    if (!that.targetEncoding || that.targetEncoding === encoding) {
                        cb(null, rawSrc);
                    } else {
                        cb(null, new iconv.Iconv(originalEncoding, that.targetEncoding).convert(rawSrc));
                    }
                }));
            }
        });
    }),

    // More efficient than Base._clone for text-based assets:
    _clone: function (cb) {
        var that = this;
        that[that.getText ? 'getText' : 'getDecodedSrc'](error.passToFunction(cb, function (text) {
            cb(null, new that.constructor({
                targetEncoding: that.targetEncoding,
                decodedSrc: text
            }));
        }));
    }
});

module.exports = Text;
<|endoftext|>