<commit_before>/****************************************************************************
 * Copyright (C) 2015-2016 EPAM Systems
 *
 * This file is part of Indigo-Node binding.
 *
 * This file may be distributed and/or modified under the terms of the
 * GNU General Public License version 3 as published by the Free Software
 * Foundation and appearing in the file LICENSE.md  included in the
 * packaging of this file.
 *
 * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
 * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 ***************************************************************************/

/* declaration of modules  */
var assert = require('assert');
var path = require('path');
var local = path.join.bind(path, __dirname);

var indigo = require("../indigo-node/indigo");

console.log('indigo basic test');
console.log('start:');

var status = indigo.setOption("molfile-saving-skip-date", "1");
console.log("****** Query reload ********")
var q = indigo.loadQueryMoleculeFromFile(local("../indigo-node/molecules/q_atom_list.mol"));
var qmf1 = q.molfile();
console.log(qmf1);
var q2 = indigo.loadQueryMolecule(q.molfile());
var qmf2 = q2.molfile();
if (qmf1 != qmf2) {
	console.log("Error: reloaded query is different:");
	console.log(qmf2);
}

/* Check that queires are equivalent */
var matcher = indigo.substructureMatcher(indigo.loadMolecule("[Sc]CN[He]"));
var none1 = matcher.match(q);
var none2 = matcher.match(q2);
if (none1 || none2)
	console.log("Error: matching results are not None: " + none1 + ' ' + none2);

console.log("****** Remove constraints and reload ********");
var q = indigo.loadQueryMolecule("c1[nH]c2c(c(N)[n+]([O-])c[n]2)[n]1");
var t = indigo.loadMolecule("c1[n]c2c(N)[n+]([O-])c[n]c2[n]1[C@H]1[C@@H](O)[C@H](O)[C@H](CO)O1");

var original_smiles = q.smiles();
console.log(q.smiles());

var matcher = indigo.substructureMatcher(t);
var has_match_orig = (matcher.match(q) != null);
console.log(has_match_orig);

for (var a of q.iterateAtoms())
	a.removeConstraints("hydrogens");
console.log(q.smiles());

var has_match = (matcher.match(q) != null);
console.log(has_match);

var q2 = q.clone();
console.log(q2.smiles());
var has_match2 = (matcher.match(q2) != null)
console.log(has_match2);
if (has_match != has_match2)
	console.error("Error: query molecule match is different after cloning");

/* reload query from original smiles */
var q3 = indigo.loadQueryMolecule(original_smiles)
console.log(q3.smiles());
var has_match3 = (matcher.match(q3) != null)
console.log(has_match3);
if (has_match3 != has_match_orig)
	console.error("Error: query molecule match is different after reloading from SMILES");

console.log("****** Bad valence, smiles and unfold ********");
var m = indigo.loadMolecule("C\C=C(/N(O)=O)N(O)=O");
var sm = m.smiles();
console.log(m.smiles());
console.log(m.canonicalSmiles());
m.unfoldHydrogens();

/* If there was an exception in unfoldHydrogens then molecule should not be changed */
var sm2 = m.smiles();
if (sm2 != sm)
	console.error("Error: " + sm + " != " + sm2);

console.log("****** Serialize and atom changing ********");
var m = indigo.loadMolecule("CC[C@@H](N)\C=C/C");
console.log(m.smiles());
console.log(m.canonicalSmiles());

for (a of m.iterateAtoms())
	a.resetAtom("*");

console.log(m.smiles());
console.log(m.canonicalSmiles());

var m2 = indigo.unserialize(m.serialize());
console.log(m2.smiles());
console.log(m2.canonicalSmiles());
<commit_msg>add IndigoObject.charge, valence and UT<commit_after>/****************************************************************************
 * Copyright (C) 2015-2016 EPAM Systems
 *
 * This file is part of Indigo-Node binding.
 *
 * This file may be distributed and/or modified under the terms of the
 * GNU General Public License version 3 as published by the Free Software
 * Foundation and appearing in the file LICENSE.md  included in the
 * packaging of this file.
 *
 * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
 * WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 ***************************************************************************/

/* declaration of modules  */
var assert = require('assert');
var path = require('path');
var local = path.join.bind(path, __dirname);

var indigo = require("../indigo-node/indigo");

console.log('indigo basic test');
console.log('start:');

var status = indigo.setOption("molfile-saving-skip-date", "1");
console.log("****** Query reload ********")
var q = indigo.loadQueryMoleculeFromFile(local("../indigo-node/molecules/q_atom_list.mol"));
var qmf1 = q.molfile();
console.log(qmf1);
var q2 = indigo.loadQueryMolecule(q.molfile());
var qmf2 = q2.molfile();
if (qmf1 != qmf2) {
	console.log("Error: reloaded query is different:");
	console.log(qmf2);
}

/* Check that queires are equivalent */
var matcher = indigo.substructureMatcher(indigo.loadMolecule("[Sc]CN[He]"));
var none1 = matcher.match(q);
var none2 = matcher.match(q2);
if (none1 || none2)
	console.log("Error: matching results are not None: " + none1 + ' ' + none2);

console.log("****** Remove constraints and reload ********");
var q = indigo.loadQueryMolecule("c1[nH]c2c(c(N)[n+]([O-])c[n]2)[n]1");
var t = indigo.loadMolecule("c1[n]c2c(N)[n+]([O-])c[n]c2[n]1[C@H]1[C@@H](O)[C@H](O)[C@H](CO)O1");

var original_smiles = q.smiles();
console.log(q.smiles());

var matcher = indigo.substructureMatcher(t);
var has_match_orig = (matcher.match(q) != null);
console.log(has_match_orig);

for (var a of q.iterateAtoms())
	a.removeConstraints("hydrogens");
console.log(q.smiles());

var has_match = (matcher.match(q) != null);
console.log(has_match);

var q2 = q.clone();
console.log(q2.smiles());
var has_match2 = (matcher.match(q2) != null)
console.log(has_match2);
if (has_match != has_match2)
	console.error("Error: query molecule match is different after cloning");

/* reload query from original smiles */
var q3 = indigo.loadQueryMolecule(original_smiles)
console.log(q3.smiles());
var has_match3 = (matcher.match(q3) != null)
console.log(has_match3);
if (has_match3 != has_match_orig)
	console.error("Error: query molecule match is different after reloading from SMILES");

console.log("****** Bad valence, smiles and unfold ********");
var m = indigo.loadMolecule("C\C=C(/N(O)=O)N(O)=O");
var sm = m.smiles();
console.log(m.smiles());
console.log(m.canonicalSmiles());
m.unfoldHydrogens();

/* If there was an exception in unfoldHydrogens then molecule should not be changed */
var sm2 = m.smiles();
if (sm2 != sm)
	console.error("Error: " + sm + " != " + sm2);

console.log("****** Serialize and atom changing ********");
var m = indigo.loadMolecule("CC[C@@H](N)\C=C/C");
console.log(m.smiles());
console.log(m.canonicalSmiles());

for (a of m.iterateAtoms())
	a.resetAtom("*");

console.log(m.smiles());
console.log(m.canonicalSmiles());

var m2 = indigo.unserialize(m.serialize());
console.log(m2.smiles());
console.log(m2.canonicalSmiles());

console.log("****** Anormal properties ********")
var m = indigo.loadMolecule("[WH7][W][W][W+10][W][W-10]")
for (a of m.iterateAtoms())
console.log(" "+a.charge()+" "+a.valence());

m2 = indigo.unserialize(m.serialize());
console.log(m2.smiles())
console.log(m2.canonicalSmiles())
for (a of m2.iterateAtoms())
console.log(" "+a.charge()+" "+a.valence());
<|endoftext|>


<commit_before>/**
 * 七牛本地文件上传模块，需要node>=7.6
 * @author VenDream
 * @since 2018-9-28
 */

const fs = require('fs');
const path = require('path');
const parse = require('url-parse');
const qiniu = require('qiniu');
const colors = require('colors/safe');
const configFile = path.resolve(__dirname, 'var/static.config.json');

// 读取七牛上传配置
let staticConfig;
let qiniuConfig;
try {
  staticConfig = require(configFile);
  qiniuConfig = staticConfig.qiniu;
} catch (err) {
  die(err);
}
const { cdnPath, distDir } = staticConfig;

// 上传前端静态输出文件到七牛CDN
function uploadToQiniu(dir) {
  // 计数器
  let uploaded = 0;
  // 解析bucket前缀
  const cdnPrefix = parse(cdnPath).pathname.slice(1);
  // 获取所有需要上传文件的本地路径
  const staticDistDir = dir || path.resolve(distDir);
  const files = getAllFiles(staticDistDir);
  logSucc(`[INFO] Found ${files.length} files`);
  // 生成七牛上传配置，参考：https://developer.qiniu.com/kodo/sdk/1289/nodejs#upload-flow
  const mac = new qiniu.auth.digest.Mac(
    qiniuConfig.accessKey,
    qiniuConfig.secretKey
  );
  const options = { scope: qiniuConfig.bucket };
  // 逐个上传
  for (const file of files) {
    const putPolicy = new qiniu.rs.PutPolicy(options);
    const uploadToken = putPolicy.uploadToken(mac);
    const formUploader = new qiniu.form_up.FormUploader();
    const putExtra = new qiniu.form_up.PutExtra();
    // 上传结果处理
    function uploaderHandler(respErr, respBody, respInfo) {
      if (respErr) {
        logErr(respErr.message);
      }
      const { statusCode } = respInfo;
      const cdnFilename = cdnPath + file.name;
      if (+statusCode === 200) {
        uploaded += 1;
        logSucc(`[${uploaded}/${files.length}] ${file.path} => ${cdnFilename}`);
        if (uploaded === files.length) {
          logSucc('[INFO] All files uploaded');
        }
      } else {
        logErr('[ERROR]', statusCode, respBody);
      }
    }
    // 执行上传动作
    formUploader.putFile(
      uploadToken,
      cdnPrefix + file.name,
      file.path,
      putExtra,
      uploaderHandler
    );
  }
}
uploadToQiniu();

// 从指定目录读取所有文件
function getAllFiles(dir, _files) {
  _files = _files || [];
  const files = fs.readdirSync(dir);
  for (const file of files) {
    const pathname = dir + path.sep + file;
    if (fs.statSync(pathname).isDirectory()) {
      getAllFiles(pathname, _files);
    } else {
      _files.push({
        path: pathname,
        name: path.relative(distDir, pathname).replace(/\\/g, '/'),
      });
    }
  }
  return _files;
}

// 打印错误并退出
function die(err) {
  logErr(err.message);
  process.exit(1);
}
// 打印成功信息
function logSucc(...msg) {
  console.log(colors.green(...msg));
}
// 打印失败信息
function logErr(...msg) {
  console.error(colors.red(...msg));
}

module.exports = uploadToQiniu;
<commit_msg>fix(upload): adjust qiniu upload logging info<commit_after>/**
 * 七牛本地文件上传模块，需要node>=7.6
 * @author VenDream
 * @since 2018-9-28
 */

const fs = require('fs');
const path = require('path');
const parse = require('url-parse');
const qiniu = require('qiniu');
const colors = require('colors/safe');
const configFile = path.resolve(__dirname, 'var/static.config.json');

// 读取七牛上传配置
let staticConfig;
let qiniuConfig;
try {
  staticConfig = require(configFile);
  qiniuConfig = staticConfig.qiniu;
} catch (err) {
  die(err);
}
const { cdnPath, distDir } = staticConfig;

// 上传前端静态输出文件到七牛CDN
function uploadToQiniu(dir) {
  // 计数器
  let uploaded = 0;
  // 解析bucket前缀
  const cdnPrefix = parse(cdnPath).pathname.slice(1);
  // 获取所有需要上传文件的本地路径
  const staticDistDir = dir || path.resolve(distDir);
  const files = getAllFiles(staticDistDir);
  logSucc(`[INFO] Found ${files.length} files`);
  // 生成七牛上传配置，参考：https://developer.qiniu.com/kodo/sdk/1289/nodejs#upload-flow
  const mac = new qiniu.auth.digest.Mac(
    qiniuConfig.accessKey,
    qiniuConfig.secretKey
  );
  const options = { scope: qiniuConfig.bucket };
  // 逐个上传
  for (const file of files) {
    const putPolicy = new qiniu.rs.PutPolicy(options);
    const uploadToken = putPolicy.uploadToken(mac);
    const formUploader = new qiniu.form_up.FormUploader();
    const putExtra = new qiniu.form_up.PutExtra();
    // 上传结果处理
    function uploaderHandler(respErr, respBody, respInfo) {
      if (respErr) {
        logErr(respErr.message);
      }
      const { statusCode } = respInfo;
      const cdnFilename = cdnPath + file.name;
      if (+statusCode === 200 || +statusCode === 614) {
        uploaded += 1;
        const isExisted = +statusCode === 614;
        const succInfo = isExisted
          ? `[${uploaded}/${files.length}] ${cdnFilename} exists`
          : `[${uploaded}/${files.length}] ${file.path} => ${cdnFilename}`;
        logSucc(succInfo);
        uploaded === files.length && logSucc('[INFO] All files uploaded');
      } else {
        logErr('[ERROR]', statusCode, respBody);
      }
    }
    // 执行上传动作
    formUploader.putFile(
      uploadToken,
      cdnPrefix + file.name,
      file.path,
      putExtra,
      uploaderHandler
    );
  }
}
uploadToQiniu();

// 从指定目录读取所有文件
function getAllFiles(dir, _files) {
  _files = _files || [];
  const files = fs.readdirSync(dir);
  for (const file of files) {
    const pathname = dir + path.sep + file;
    if (fs.statSync(pathname).isDirectory()) {
      getAllFiles(pathname, _files);
    } else {
      _files.push({
        path: pathname,
        name: path.relative(distDir, pathname).replace(/\\/g, '/'),
      });
    }
  }
  return _files;
}

// 打印错误并退出
function die(err) {
  logErr(err.message);
  process.exit(1);
}
// 打印成功信息
function logSucc(...msg) {
  console.log(colors.green(...msg));
}
// 打印失败信息
function logErr(...msg) {
  console.error(colors.red(...msg));
}

module.exports = uploadToQiniu;
<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import url from 'url';
import DropdownButton from '../libs/bootstrap/button';
import { DropdownMenu } from '../libs/bootstrap/dropdown-menu';
import { Panel, PanelBody } from '../libs/bootstrap/panel';
import { FacetList, Listing } from './search';
import { FetchedData, Param } from './fetched';
import * as globals from './globals';

const regionGenomes = [
    { value: 'GRCh37', display: 'hg19' },
    { value: 'GRCh38', display: 'GRCh38' },
    { value: 'GRCm37', display: 'mm9' },
    { value: 'GRCm38', display: 'mm10' },
];

const AutocompleteBox = (props) => {
    const terms = props.auto['@graph']; // List of matching terms from server
    const handleClick = props.handleClick;
    const userTerm = props.userTerm && props.userTerm.toLowerCase(); // Term user entered

    if (!props.hide && userTerm && userTerm.length && terms && terms.length) {
        return (

                {terms.map((term) => {
                    let matchEnd;
                    let preText;
                    let matchText;
                    let postText;

                    // Boldface matching part of term
                    const matchStart = term.text.toLowerCase().indexOf(userTerm);
                    if (matchStart >= 0) {
                        matchEnd = matchStart + userTerm.length;
                        preText = term.text.substring(0, matchStart);
                        matchText = term.text.substring(matchStart, matchEnd);
                        postText = term.text.substring(matchEnd);
                    } else {
                        preText = term.text;
                    }
                    return (
                        <AutocompleteBoxMenu
                            key={term.text}
                            handleClick={handleClick}
                            term={term}
                            name={props.name}
                            preText={preText}
                            matchText={matchText}
                            postText={postText}
                        />
                    );
                }, this)}

        );
    }

    return null;
};

AutocompleteBox.propTypes = {
    auto: PropTypes.object,
    userTerm: PropTypes.string,
    handleClick: PropTypes.func,
    hide: PropTypes.bool,
    name: PropTypes.string,
};

AutocompleteBox.defaultProps = {
    auto: {}, // Looks required, but because it's built from <Param>, it can fail type checks.
    userTerm: '',
    handleClick: null,
    hide: false,
    name: '',
};

// Draw the autocomplete box drop-down menu.
class AutocompleteBoxMenu extends React.Component {
    constructor() {
        super();

        // Bind this to non-React methods.
        this.handleClick = this.handleClick.bind(this);
    }

    // Handle clicks in the drop-down menu. It just calls the parent's handleClick function, giving
    // it the parameters of the clicked item.
    handleClick() {
        const { term, name } = this.props;
        this.props.handleClick(term.text, term.payload.id, name);
    }

    render() {
        const { preText, matchText, postText } = this.props;

        return (

                {preText}{matchText}{postText}

        );
    }
}

AutocompleteBoxMenu.propTypes = {
    handleClick: PropTypes.func.isRequired, // Parent function to handle a click in a drop-down menu item
    term: PropTypes.object.isRequired, // Object for the term being searched
    name: PropTypes.string,
    preText: PropTypes.string, // Text before the matched term in the entered string
    matchText: PropTypes.string, // Matching text in the entered string
    postText: PropTypes.string, // Text after the matched term in the entered string
};

AutocompleteBoxMenu.defaultProps = {
    name: '',
    preText: '',
    matchText: '',
    postText: '',
};

class AdvSearch extends React.Component {
    constructor() {
        super();

        // Set intial React state.
        this.state = {
            disclosed: false,
            showAutoSuggest: false,
            searchTerm: '',
            coordinates: '',
            genome: regionGenomes[0].value,
            terms: {},
        };

        // Bind this to non-React methods.
        this.handleDiscloseClick = this.handleDiscloseClick.bind(this);
        this.handleChange = this.handleChange.bind(this);
        this.handleAutocompleteClick = this.handleAutocompleteClick.bind(this);
        this.handleAssemblySelect = this.handleAssemblySelect.bind(this);
        this.tick = this.tick.bind(this);
    }

    componentDidMount() {
        // Use timer to limit to one request per second
        this.timer = setInterval(this.tick, 1000);
    }

    componentWillUnmount() {
        clearInterval(this.timer);
    }

    handleDiscloseClick() {
        this.setState(prevState => ({
            disclosed: !prevState.disclosed,
        }));
    }

    handleChange(e) {
        this.setState({ showAutoSuggest: true, terms: {} });
        this.newSearchTerm = e.target.value;
    }

    handleAutocompleteClick(term, id, name) {
        const newTerms = {};
        const inputNode = this.annotation;
        inputNode.value = term;
        newTerms[name] = id;
        this.setState({ terms: newTerms, showAutoSuggest: false });
        inputNode.focus();
        // Now let the timer update the terms state when it gets around to it.
    }

    handleAssemblySelect(event) {
        // Handle click in assembly-selection <select>
        this.setState({ genome: event.target.value });
    }

    tick() {
        if (this.newSearchTerm !== this.state.searchTerm) {
            this.setState({ searchTerm: this.newSearchTerm });
        }
    }

    render() {
        const context = this.props.context;
        const id = url.parse(this.context.location_href, true);
        const region = id.query.region || '';

        return (
            <Panel>
                <PanelBody>
                    <form id="panel1" className="adv-search-form" ref="adv-search" role="form" autoComplete="off" aria-labelledby="tab1">
                        <input type="hidden" name="annotation" value={this.state.terms.annotation} />

                            Enter any one of human Gene name, Symbol, Synonyms, Gene ID, HGNC ID, coordinates, rsid, Ensemble ID

                                <input id="annotation" ref={(input) => { this.annotation = input; }} defaultValue={region} name="region" type="text" className="form-control" onChange={this.handleChange} />
                                {(this.state.showAutoSuggest && this.state.searchTerm) ?
                                    <FetchedData loadingComplete>
                                        <Param name="auto" url={`/suggest/?genome=${this.state.genome}&q=${this.state.searchTerm}`} type="json" />
                                        <AutocompleteBox name="annotation" userTerm={this.state.searchTerm} handleClick={this.handleAutocompleteClick} />
                                    </FetchedData>
                                : null}

                                    <select value={this.state.genome} name="genome" onChange={this.handleAssemblySelect}>
                                        {regionGenomes.map(genomeId =>
                                            <option key={genomeId.value} value={genomeId.value}>{genomeId.display}</option>
                                        )}
                                    </select>

                                {context.notification ?

{context.notification}

                                : null}

                        <input type="submit" value="Search" className="btn btn-sm btn-info pull-right" />
                    </form>
                    {context.coordinates ?

Searched coordinates: {context.coordinates}

                    : null}
                </PanelBody>
            </Panel>
        );
    }
}

AdvSearch.propTypes = {
    context: PropTypes.object.isRequired,
};

AdvSearch.contextTypes = {
    autocompleteTermChosen: PropTypes.bool,
    autocompleteHidden: PropTypes.bool,
    onAutocompleteHiddenChange: PropTypes.func,
    location_href: PropTypes.string,
};

class RegionSearch extends React.Component {
    constructor() {
        super();

        // Bind this to non-React methods.
        this.onFilter = this.onFilter.bind(this);
    }

    onFilter(e) {
        if (this.props.onChange) {
            const search = e.currentTarget.getAttribute('href');
            this.props.onChange(search);
            e.stopPropagation();
            e.preventDefault();
        }
    }

    render() {
        const visualizeLimit = 100;
        const context = this.props.context;
        const results = context['@graph'];
        const columns = context.columns;
        const notification = context.notification;
        const searchBase = url.parse(this.context.location_href).search || '';
        const trimmedSearchBase = searchBase.replace(/[?|&]limit=all/, '');
        const filters = context.filters;
        const facets = context.facets;
        const total = context.total;
        const visualizeDisabled = total > visualizeLimit;

        // Get a sorted list of batch hubs keys with case-insensitive sort
        let visualizeKeys = [];
        if (context.visualize_batch && Object.keys(context.visualize_batch).length) {
            visualizeKeys = Object.keys(context.visualize_batch).sort((a, b) => {
                const aLower = a.toLowerCase();
                const bLower = b.toLowerCase();
                return (aLower > bLower) ? 1 : ((aLower < bLower) ? -1 : 0);
            });
        }

        return (

Region search

                <AdvSearch {...this.props} />
                    {notification === 'Success' ?

                                    <FacetList
                                        {...this.props}
                                        facets={facets}
                                        filters={filters}
                                        searchBase={searchBase ? `${searchBase}&` : `${searchBase}?`}
                                        onFilter={this.onFilter}
                                    />

                                            Showing {results.length} of {total}

                                            {total > results.length && searchBase.indexOf('limit=all') === -1 ?
                                                    <a
                                                        rel="nofollow"
                                                        className="btn btn-info btn-sm"
                                                        href={searchBase ? `${searchBase}&limit=all` : '?limit=all'}
                                                        onClick={this.onFilter}
                                                    >
                                                        View All

                                            :

                                                    {results.length > 25 ?

                                                                View 25

                                                    : null}

                                            }

                                            {context.download_elements ?
                                                <DropdownButton title="Download Elements" label="downloadelements" wrapperClasses="results-table-button">
                                                    <DropdownMenu>
                                                        {context.download_elements.map(link =>

                                                                {link.split('.').pop()}

                                                        )}
                                                    </DropdownMenu>
                                                </DropdownButton>
                                            : null}

                                            {visualizeKeys ?
                                                <DropdownButton disabled={visualizeDisabled} title={visualizeDisabled ? `Filter to ${visualizeLimit} to visualize` : 'Visualize'} label="batchhubs" wrapperClasses="results-table-button">
                                                    <DropdownMenu>
                                                        {visualizeKeys.map(assembly =>
                                                            Object.keys(context.visualize_batch[assembly]).sort().map(browser =>

                                                                    {assembly} {browser}

                                                            )
                                                        )}
                                                    </DropdownMenu>
                                                </DropdownButton>
                                            : null}

                                      {results.map(result => Listing({ context: result, columns, key: result['@id'] }))}

                    : null}

        );
    }
}

RegionSearch.propTypes = {
    context: PropTypes.object.isRequired,
    onChange: PropTypes.func,
};

RegionSearch.defaultProps = {
    onChange: null,
};

RegionSearch.contextTypes = {
    location_href: PropTypes.string,
    navigate: PropTypes.func,
};

globals.contentViews.register(RegionSearch, 'region-search');
<commit_msg>Remove Download Elements button<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import url from 'url';
import DropdownButton from '../libs/bootstrap/button';
import { DropdownMenu } from '../libs/bootstrap/dropdown-menu';
import { Panel, PanelBody } from '../libs/bootstrap/panel';
import { FacetList, Listing } from './search';
import { FetchedData, Param } from './fetched';
import * as globals from './globals';

const regionGenomes = [
    { value: 'GRCh37', display: 'hg19' },
    { value: 'GRCh38', display: 'GRCh38' },
    { value: 'GRCm37', display: 'mm9' },
    { value: 'GRCm38', display: 'mm10' },
];

const AutocompleteBox = (props) => {
    const terms = props.auto['@graph']; // List of matching terms from server
    const handleClick = props.handleClick;
    const userTerm = props.userTerm && props.userTerm.toLowerCase(); // Term user entered

    if (!props.hide && userTerm && userTerm.length && terms && terms.length) {
        return (

                {terms.map((term) => {
                    let matchEnd;
                    let preText;
                    let matchText;
                    let postText;

                    // Boldface matching part of term
                    const matchStart = term.text.toLowerCase().indexOf(userTerm);
                    if (matchStart >= 0) {
                        matchEnd = matchStart + userTerm.length;
                        preText = term.text.substring(0, matchStart);
                        matchText = term.text.substring(matchStart, matchEnd);
                        postText = term.text.substring(matchEnd);
                    } else {
                        preText = term.text;
                    }
                    return (
                        <AutocompleteBoxMenu
                            key={term.text}
                            handleClick={handleClick}
                            term={term}
                            name={props.name}
                            preText={preText}
                            matchText={matchText}
                            postText={postText}
                        />
                    );
                }, this)}

        );
    }

    return null;
};

AutocompleteBox.propTypes = {
    auto: PropTypes.object,
    userTerm: PropTypes.string,
    handleClick: PropTypes.func,
    hide: PropTypes.bool,
    name: PropTypes.string,
};

AutocompleteBox.defaultProps = {
    auto: {}, // Looks required, but because it's built from <Param>, it can fail type checks.
    userTerm: '',
    handleClick: null,
    hide: false,
    name: '',
};

// Draw the autocomplete box drop-down menu.
class AutocompleteBoxMenu extends React.Component {
    constructor() {
        super();

        // Bind this to non-React methods.
        this.handleClick = this.handleClick.bind(this);
    }

    // Handle clicks in the drop-down menu. It just calls the parent's handleClick function, giving
    // it the parameters of the clicked item.
    handleClick() {
        const { term, name } = this.props;
        this.props.handleClick(term.text, term.payload.id, name);
    }

    render() {
        const { preText, matchText, postText } = this.props;

        return (

                {preText}{matchText}{postText}

        );
    }
}

AutocompleteBoxMenu.propTypes = {
    handleClick: PropTypes.func.isRequired, // Parent function to handle a click in a drop-down menu item
    term: PropTypes.object.isRequired, // Object for the term being searched
    name: PropTypes.string,
    preText: PropTypes.string, // Text before the matched term in the entered string
    matchText: PropTypes.string, // Matching text in the entered string
    postText: PropTypes.string, // Text after the matched term in the entered string
};

AutocompleteBoxMenu.defaultProps = {
    name: '',
    preText: '',
    matchText: '',
    postText: '',
};

class AdvSearch extends React.Component {
    constructor() {
        super();

        // Set intial React state.
        this.state = {
            disclosed: false,
            showAutoSuggest: false,
            searchTerm: '',
            coordinates: '',
            genome: regionGenomes[0].value,
            terms: {},
        };

        // Bind this to non-React methods.
        this.handleDiscloseClick = this.handleDiscloseClick.bind(this);
        this.handleChange = this.handleChange.bind(this);
        this.handleAutocompleteClick = this.handleAutocompleteClick.bind(this);
        this.handleAssemblySelect = this.handleAssemblySelect.bind(this);
        this.tick = this.tick.bind(this);
    }

    componentDidMount() {
        // Use timer to limit to one request per second
        this.timer = setInterval(this.tick, 1000);
    }

    componentWillUnmount() {
        clearInterval(this.timer);
    }

    handleDiscloseClick() {
        this.setState(prevState => ({
            disclosed: !prevState.disclosed,
        }));
    }

    handleChange(e) {
        this.setState({ showAutoSuggest: true, terms: {} });
        this.newSearchTerm = e.target.value;
    }

    handleAutocompleteClick(term, id, name) {
        const newTerms = {};
        const inputNode = this.annotation;
        inputNode.value = term;
        newTerms[name] = id;
        this.setState({ terms: newTerms, showAutoSuggest: false });
        inputNode.focus();
        // Now let the timer update the terms state when it gets around to it.
    }

    handleAssemblySelect(event) {
        // Handle click in assembly-selection <select>
        this.setState({ genome: event.target.value });
    }

    tick() {
        if (this.newSearchTerm !== this.state.searchTerm) {
            this.setState({ searchTerm: this.newSearchTerm });
        }
    }

    render() {
        const context = this.props.context;
        const id = url.parse(this.context.location_href, true);
        const region = id.query.region || '';

        return (
            <Panel>
                <PanelBody>
                    <form id="panel1" className="adv-search-form" ref="adv-search" role="form" autoComplete="off" aria-labelledby="tab1">
                        <input type="hidden" name="annotation" value={this.state.terms.annotation} />

                            Enter any one of human Gene name, Symbol, Synonyms, Gene ID, HGNC ID, coordinates, rsid, Ensemble ID

                                <input id="annotation" ref={(input) => { this.annotation = input; }} defaultValue={region} name="region" type="text" className="form-control" onChange={this.handleChange} />
                                {(this.state.showAutoSuggest && this.state.searchTerm) ?
                                    <FetchedData loadingComplete>
                                        <Param name="auto" url={`/suggest/?genome=${this.state.genome}&q=${this.state.searchTerm}`} type="json" />
                                        <AutocompleteBox name="annotation" userTerm={this.state.searchTerm} handleClick={this.handleAutocompleteClick} />
                                    </FetchedData>
                                : null}

                                    <select value={this.state.genome} name="genome" onChange={this.handleAssemblySelect}>
                                        {regionGenomes.map(genomeId =>
                                            <option key={genomeId.value} value={genomeId.value}>{genomeId.display}</option>
                                        )}
                                    </select>

                                {context.notification ?

{context.notification}

                                : null}

                        <input type="submit" value="Search" className="btn btn-sm btn-info pull-right" />
                    </form>
                    {context.coordinates ?

Searched coordinates: {context.coordinates}

                    : null}
                </PanelBody>
            </Panel>
        );
    }
}

AdvSearch.propTypes = {
    context: PropTypes.object.isRequired,
};

AdvSearch.contextTypes = {
    autocompleteTermChosen: PropTypes.bool,
    autocompleteHidden: PropTypes.bool,
    onAutocompleteHiddenChange: PropTypes.func,
    location_href: PropTypes.string,
};

class RegionSearch extends React.Component {
    constructor() {
        super();

        // Bind this to non-React methods.
        this.onFilter = this.onFilter.bind(this);
    }

    onFilter(e) {
        if (this.props.onChange) {
            const search = e.currentTarget.getAttribute('href');
            this.props.onChange(search);
            e.stopPropagation();
            e.preventDefault();
        }
    }

    render() {
        const visualizeLimit = 100;
        const context = this.props.context;
        const results = context['@graph'];
        const columns = context.columns;
        const notification = context.notification;
        const searchBase = url.parse(this.context.location_href).search || '';
        const trimmedSearchBase = searchBase.replace(/[?|&]limit=all/, '');
        const filters = context.filters;
        const facets = context.facets;
        const total = context.total;
        const visualizeDisabled = total > visualizeLimit;

        // Get a sorted list of batch hubs keys with case-insensitive sort
        let visualizeKeys = [];
        if (context.visualize_batch && Object.keys(context.visualize_batch).length) {
            visualizeKeys = Object.keys(context.visualize_batch).sort((a, b) => {
                const aLower = a.toLowerCase();
                const bLower = b.toLowerCase();
                return (aLower > bLower) ? 1 : ((aLower < bLower) ? -1 : 0);
            });
        }

        return (

Region search

                <AdvSearch {...this.props} />
                    {notification === 'Success' ?

                                    <FacetList
                                        {...this.props}
                                        facets={facets}
                                        filters={filters}
                                        searchBase={searchBase ? `${searchBase}&` : `${searchBase}?`}
                                        onFilter={this.onFilter}
                                    />

                                            Showing {results.length} of {total}

                                            {total > results.length && searchBase.indexOf('limit=all') === -1 ?
                                                    <a
                                                        rel="nofollow"
                                                        className="btn btn-info btn-sm"
                                                        href={searchBase ? `${searchBase}&limit=all` : '?limit=all'}
                                                        onClick={this.onFilter}
                                                    >
                                                        View All

                                            :

                                                    {results.length > 25 ?

                                                                View 25

                                                    : null}

                                            }

                                            {visualizeKeys ?
                                                <DropdownButton disabled={visualizeDisabled} title={visualizeDisabled ? `Filter to ${visualizeLimit} to visualize` : 'Visualize'} label="batchhubs" wrapperClasses="results-table-button">
                                                    <DropdownMenu>
                                                        {visualizeKeys.map(assembly =>
                                                            Object.keys(context.visualize_batch[assembly]).sort().map(browser =>

                                                                    {assembly} {browser}

                                                            )
                                                        )}
                                                    </DropdownMenu>
                                                </DropdownButton>
                                            : null}

                                      {results.map(result => Listing({ context: result, columns, key: result['@id'] }))}

                    : null}

        );
    }
}

RegionSearch.propTypes = {
    context: PropTypes.object.isRequired,
    onChange: PropTypes.func,
};

RegionSearch.defaultProps = {
    onChange: null,
};

RegionSearch.contextTypes = {
    location_href: PropTypes.string,
    navigate: PropTypes.func,
};

globals.contentViews.register(RegionSearch, 'region-search');
<|endoftext|>


<commit_before>window.urb.seqn_u = 1
window.urb.dely = 0
window.urb.puls = false
window.urb.cabs = {}
if(!window.urb.appl) window.urb.appl = null

window.urb.req = function(method,url,params,json,cb) {
  var xhr = new XMLHttpRequest()
  method = method.toUpperCase()
  if(method == "PUT" || method == "DELETE")
    xhr.open("POST", url+"?"+method)
  else xhr.open(method, url)

  if(json)
    xhr.setRequestHeader("content-type", "text/json")

  if(!window.urb.oryx) throw "No CSRF token" // XX fetch auth.json
  _data = {oryx: window.urb.oryx}
  if(params.xyro) { _data.xyro = params.xyro; }
  if(params.ship) { _data.ship = params.ship; }
  if(params.path) { _data.path = params.path; }
  if(params.appl) { _data.appl = params.appl; }
  if(params.mark) { _data.mark = params.mark; }
  if(params.wire) { _data.wire = params.wire; }
  if(cb) {
    xhr.onload = function() {
      try {
        err = null
        res = {
          status:this.status,
          data: JSON.parse(this.responseText)
        }
        if(res.data.reload)
          res.reload = res.data.reload
      } catch(e) {
        // if(urb.wall !== false) document.write(this.responseText) // XX
        err = {
          message:"Failed to parse JSON",
          raw:this.responseText
        }
        res = null
      }
      cb(err,res)
    }
    xhr.onerror = function() {
      cb({
        status:this.status,
        data:this.responseText
      })
    }
  }
  xhr.send(JSON.stringify(_data))
}

// window.urb.getJSON = function(url,cb){ window.urb.reqJSON("GET",url, null, cb)}
// window.urb.reqJSON = function(method, url, data, cb){
//   var xhr = new XMLHttpRequest()
//   xhr.open(method, url)
//   xhr.onload = function(){
//     urb.fetchTag.call(xhr)
//     if(cb) cb(JSON.parse(xhr.responseText))
//   }
//   xhr.send(data === null ? null : JSON.stringify(data))
// }

window.urb.reqq = []
window.urb.qreq = function(method,url,params,json,cb) {
  walk = function() {
    qobj = {}
    qobj.oargs = window.urb.reqq[0]
    qobj.nargs = [].slice.apply(qobj.oargs,[0,4])
    qobj.nargs.push(function(){
      if(this.oargs[4])
        this.oargs[4].apply(window.urb,arguments)
      window.urb.reqq.shift()
      if(window.urb.reqq.length > 0)
        walk()
    }.bind(qobj))
    window.urb.req.apply(this,qobj.nargs)
  }
  l = window.urb.reqq.length
  window.urb.reqq.push(arguments);
  if(l == 0) { walk() }
}

window.urb.gsig = function(params) {
  var path = params.path
  if(!path) path = ""
  if(path[0] !== "/") path = "/"+path
  return  "~"+params.ship+"/"+
          params.appl+
          path.replace(/[^\x00-\x7F]/g, "")
}

window.urb.poll = function(params) {
  if(!params) throw new Error("You must supply params to urb.poll.")

  var url, $this

  seqn = this.seqn_u
  if(params.seqn) seqn = params.seqn()

  url = "/~/of/"+this.ixor+"?poll="+seqn

  this.puls = true

  $this = this
  this.req("get",url,params,true,function(err,res) {
    if(res){
      if(res.data.beat)
        return $this.poll(params)
      switch(res.data.type){
          case "news":
        return document.location.reload()  // XX check autoreload
          case "rush":
          case "mean":
        var err2 = err
        if(res.data.type == "mean")
          err2 = res.data.data
        var fn = $this.gsig(res.data.from)
        if($this.cabs[fn])
          $this.cabs[fn].call(this,err2,
            {status: res.status, data: res.data.data.json}) // XX non-json
        break;
          default:
        throw new Error("Lost event %"+res.data.type)
      }
    }

    dely = params.dely ? params.dely : $this.dely

    if(err)
      dely = dely+Math.ceil(dely*.02)
    else {
      $this.dely = 0
      if(params.incs)
        params.incs()
      else
        $this.seqn_u++
    }

    setTimeout(function() {
      $this.poll(params)
    },dely)
  })
}

// if (window.urb.auto) {  // need dependencies
//   var tries = 0
//   var cnt = 0
//   var param = {
//     type:"pol"
//   }
//   window.urb.poll(param)
// }

// window.urb.heartbeat = function() {
//   this.poll({
//     type:"heb",
//     ship:this.ship,
//     dely:30000,
//     seqn:function() {
//       return window.urb.seqn_h
//     },
//     incs:function() {
//       window.urb.seqn_h = window.urb.seqn_h+1
//     }
//   },function() {
//     console.log('heartbeat.')
//   })
// }
// window.urb.heartbeat()

//  //  /  //  /  //  //
// end old %eyre code //
//  //  /  //  /  //  //

window.urb.seqn_s = 0

// TODO urb.send(data, [params/params.appl]?, cb?)
window.urb.send = function(params,cb) {
  if(!params)
    throw new Error("You must supply params to urb.send.")
  if(!params.appl && !this.appl){
    throw new Error("You must specify an appl for urb.send.")
  }

  var url, $this

  params.ship = params.ship ? params.ship : this.ship
  params.appl = params.appl ? params.appl : this.appl
  params.mark = params.mark ? params.mark : "json"
  params.xyro = params.data ? params.data : {}
  params.wire = params.wire ? params.wire : "/"

  url = ["to",params.appl,params.mark]
  url = "/~/"+url.join("/")

  this.seqn_s++

  $this = this
  this.qreq('post',url,params,true,function(err,data) {
    if(err) { $this.seqn_s--; }
    else if(data && data.data.fail && urb.wall !== false)
      document.write("
"+JSON.stringify(params.xyro)+"\n"
                            +data.data.mess+"
") // XX
    if(cb) { cb.apply(this,arguments); }
  })
}

window.urb.subscribe = function(params,cb) {   //  legacy interface
  if(!params) throw new Error("You must supply params to urb.subscribe")
  return window.urb.bind(params.path, params, cb, cb)
}
window.urb.bind = function(path, cb){ // or bind(path, params, cb, nicecb?)
  var params, nicecb
  if(arguments.length > 2)
    {params = cb; cb = arguments[2], nicecb = arguments[3]}
  else params = {}

  params.path = path
  if(params.path[0] !== "/") params.path = "/"+params.path
  params.ship = params.ship ? params.ship : this.ship
  params.appl = params.appl ? params.appl : this.appl
  params.mark = params.mark ? params.mark : "json"
  params.wire = params.wire ? params.wire : params.path

  if(typeof path != "string")
    throw new Error("You must specify a string path for urb.bind.")
  if(!params.appl) throw new Error("You must specify an appl for urb.bind.")
  if(!cb) throw new Error("You must supply a callback to urb.bind.")

  var method, perm, url, $this

  if(params.mark !== "json")
    throw new Error("Non-json subscriptions unimplemented.")  //  XX
  url = "/~/is/"+this.gsig(params)+"."+params.mark

  params.path = params.wire
  this.cabs[this.gsig(params)] = cb

  $this = this
  this.qreq("put",url,params,true,function(err,res) {
    if(nicecb) { nicecb.apply(this,[err,{status: res.status, data: res.data}])}
    //  XX give raw data
    //
    if(!err && !$this.puls) $this.poll(params)
  })
}

window.urb.unsubscribe = function(params,cb) {
  if(!params) throw new Error("You must supply params to urb.unsubscribe.")

  params.ship = params.ship ? params.ship : this.ship
  params.appl = params.appl ? params.appl : this.appl
  params.wire = params.wire ? params.wire : params.path

  if(!params.path) throw new Error("You must specify a path for urb.unsubscribe.")
  if(!params.appl) throw new Error("You must specify an appl for urb.unsubscribe.")

  url = "/~/is/"+this.gsig(params)+".json"
  method = "delete"
  this.req("delete",url,params,true,function(err,res) {
    if(cb) cb(err,res)
  })
}

window.urb.util = {
  isURL: function(s) {
     r = new RegExp('^(?!mailto:)(?:(?:http|https|ftp)://)(?:\\S+(?::\\S*)?@)?(?:(?:(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}(?:\\.(?:[0-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))|(?:(?:[a-z\\u00a1-\\uffff0-9]+-?)*[a-z\\u00a1-\\uffff0-9]+)(?:\\.(?:[a-z\\u00a1-\\uffff0-9]+-?)*[a-z\\u00a1-\\uffff0-9]+)*(?:\\.(?:[a-z\\u00a1-\\uffff]{2,})))|localhost)(?::\\d{2,5})?(?:(/|\\?|#)[^\\s]*)?$', 'i');
     return s.length < 2083 && r.test(s);
  },
  numDot: function(n) {
    _n = String(n)
    fun = function(s){
      if(s.length <= 3)
        return s
      return fun(s.slice(0,-3))+"."+s.slice(-3)
    }
    return fun((_n))
  },
  toDate: function (dat){
    var mils = Math.floor((0x10000 * dat.getUTCMilliseconds()) / 1000).toString(16)
    function pad(num, str){
      return ((new Array(num + 1)).join('0') + str).substr(-num,num)
    }
    return  '~' + dat.getUTCFullYear() +
            '.' + (dat.getUTCMonth() + 1) +
            '.' + dat.getUTCDate() +
           '..' + pad(2, dat.getUTCHours()) +
            '.' + pad(2, dat.getUTCMinutes()) +
            '.' + pad(2, dat.getUTCSeconds()) +
           '..' + pad(4, mils)
  }
}
<commit_msg>Cherry-picked urb.js additions from test<commit_after>window.urb.seqn_u = 1
window.urb.dely = 0
window.urb.puls = false
window.urb.cabs = {}
if(!window.urb.appl) window.urb.appl = null

window.urb.req = function(method,url,params,json,cb) {
  var xhr = new XMLHttpRequest()
  method = method.toUpperCase()
  if(method == "PUT" || method == "DELETE")
    xhr.open("POST", url+"?"+method)
  else xhr.open(method, url)

  if(json)
    xhr.setRequestHeader("content-type", "text/json")

  if(!window.urb.oryx) throw "No CSRF token" // XX fetch auth.json
  _data = {oryx: window.urb.oryx}
  if(params.xyro) { _data.xyro = params.xyro; }
  if(params.ship) { _data.ship = params.ship; }
  if(params.path) { _data.path = params.path; }
  if(params.appl) { _data.appl = params.appl; }
  if(params.mark) { _data.mark = params.mark; }
  if(params.wire) { _data.wire = params.wire; }
  if(cb) {
    xhr.onload = function() {
      try {
        err = null
        res = {
          status:this.status,
          data: JSON.parse(this.responseText)
        }
        if(res.data.reload)
          res.reload = res.data.reload
      } catch(e) {
        // if(urb.wall !== false) document.write(this.responseText) // XX
        err = {
          message:"Failed to parse JSON",
          raw:this.responseText
        }
        res = null
      }
      cb(err,res)
    }
    xhr.onerror = function() {
      cb({
        status:this.status,
        data:this.responseText
      })
    }
  }
  xhr.send(JSON.stringify(_data))
}

// window.urb.getJSON = function(url,cb){ window.urb.reqJSON("GET",url, null, cb)}
// window.urb.reqJSON = function(method, url, data, cb){
//   var xhr = new XMLHttpRequest()
//   xhr.open(method, url)
//   xhr.onload = function(){
//     urb.fetchTag.call(xhr)
//     if(cb) cb(JSON.parse(xhr.responseText))
//   }
//   xhr.send(data === null ? null : JSON.stringify(data))
// }

window.urb.reqq = []
window.urb.qreq = function(method,url,params,json,cb) {
  walk = function() {
    qobj = {}
    qobj.oargs = window.urb.reqq[0]
    qobj.nargs = [].slice.apply(qobj.oargs,[0,4])
    qobj.nargs.push(function(){
      if(this.oargs[4])
        this.oargs[4].apply(window.urb,arguments)
      window.urb.reqq.shift()
      if(window.urb.reqq.length > 0)
        walk()
    }.bind(qobj))
    window.urb.req.apply(this,qobj.nargs)
  }
  l = window.urb.reqq.length
  window.urb.reqq.push(arguments);
  if(l == 0) { walk() }
}

window.urb.gsig = function(params) {
  var path = params.path
  if(!path) path = ""
  if(path[0] !== "/") path = "/"+path
  return  "~"+params.ship+"/"+
          params.appl+
          path.replace(/[^\x00-\x7F]/g, "")
}

window.urb.poll = function(params) {
  if(!params) throw new Error("You must supply params to urb.poll.")

  var url, $this

  seqn = this.seqn_u
  if(params.seqn) seqn = params.seqn()

  url = "/~/of/"+this.ixor+"?poll="+seqn

  this.puls = true

  $this = this
  this.req("get",url,params,true,function(err,res) {
    if(res){
      if(res.data.beat)
        return $this.poll(params)
      switch(res.data.type){
          case "news":
        return document.location.reload()  // XX check autoreload
          case "rush":
          case "mean":
        var err2 = err
        if(res.data.type == "mean")
          err2 = res.data.data
        var fn = $this.gsig(res.data.from)
        if($this.cabs[fn])
          $this.cabs[fn].call(this,err2,
            {status: res.status, data: res.data.data.json}) // XX non-json
        break;
          default:
        throw new Error("Lost event %"+res.data.type)
      }
    }

    dely = params.dely || $this.dely

    if(err)
      dely = dely+Math.ceil(dely*.02)
    else {
      $this.dely = 0
      if(params.incs)
        params.incs()
      else
        $this.seqn_u++
    }

    setTimeout(function() {
      $this.poll(params)
    },dely)
  })
}

// if (window.urb.auto) {  // need dependencies
//   var tries = 0
//   var cnt = 0
//   var param = {
//     type:"pol"
//   }
//   window.urb.poll(param)
// }

// window.urb.heartbeat = function() {
//   this.poll({
//     type:"heb",
//     ship:this.ship,
//     dely:30000,
//     seqn:function() {
//       return window.urb.seqn_h
//     },
//     incs:function() {
//       window.urb.seqn_h = window.urb.seqn_h+1
//     }
//   },function() {
//     console.log('heartbeat.')
//   })
// }
// window.urb.heartbeat()

//  //  /  //  /  //  //
// end old %eyre code //
//  //  /  //  /  //  //

window.urb.seqn_s = 0

// TODO urb.send(data, [params/params.appl]?, cb?)
window.urb.send = function(params,cb) {
  if(!params)
    throw new Error("You must supply params to urb.send.")
  if(!params.appl && !this.appl){
    throw new Error("You must specify an appl for urb.send.")
  }

  var url, $this

  params.ship = params.ship || this.ship
  params.appl = params.appl || this.appl
  params.mark = params.mark || "json"
  params.xyro = params.data || {}
  params.wire = params.wire || "/"

  url = ["to",params.appl,params.mark]
  url = "/~/"+url.join("/")

  this.seqn_s++

  $this = this
  this.qreq('post',url,params,true,function(err,data) {
    if(err) { $this.seqn_s--; }
    else if(data && data.data.fail && urb.wall !== false)
      document.write("
"+JSON.stringify(params.xyro)+"\n"
                            +data.data.mess+"
") // XX
    if(cb) { cb.apply(this,arguments); }
  })
}

window.urb.subscribe = function(params,cb) {   //  legacy interface
  if(!params) throw new Error("You must supply params to urb.subscribe")
  return window.urb.bind(params.path, params, cb, cb)
}
window.urb.bind = function(path, cb){ // or bind(path, params, cb, nicecb?)
  var params, nicecb
  if(arguments.length > 2)
    {params = cb; cb = arguments[2], nicecb = arguments[3]}
  else params = {}

  params.path = path
  if(params.path[0] !== "/") params.path = "/"+params.path
  params.ship = params.ship || this.ship
  params.appl = params.appl || this.appl
  params.mark = params.mark || "json"
  params.wire = params.wire || params.path

  if(typeof path != "string")
    throw new Error("You must specify a string path for urb.bind.")
  if(!params.appl) throw new Error("You must specify an appl for urb.bind.")
  if(!cb) throw new Error("You must supply a callback to urb.bind.")

  var method, perm, url, $this

  if(params.mark !== "json")
    throw new Error("Non-json subscriptions unimplemented.")  //  XX
  url = "/~/is/"+this.gsig(params)+"."+params.mark

  params.path = params.wire
  this.cabs[this.gsig(params)] = cb

  $this = this
  this.qreq("put",url,params,true,function(err,res) {
    if(nicecb) { nicecb.apply(this,[err,{status: res.status, data: res.data}])}
    //  XX give raw data
    //
    if(!err && !$this.puls) $this.poll(params)
  })
}

window.urb.unsubscribe = function(params,cb) {
  if(!params) throw new Error("You must supply params to urb.unsubscribe.")

  params.ship = params.ship || this.ship
  params.appl = params.appl || this.appl
  params.wire = params.wire || params.path

  if(!params.path) throw new Error("You must specify a path for urb.unsubscribe.")
  if(!params.appl) throw new Error("You must specify an appl for urb.unsubscribe.")

  url = "/~/is/"+this.gsig(params)+".json"
  method = "delete"
  this.req("delete",url,params,true,function(err,res) {
    if(cb) cb(err,res)
  })
}

window.urb.util = {
  isURL: function(s) {
     r = new RegExp('^(?!mailto:)(?:(?:http|https|ftp)://)(?:\\S+(?::\\S*)?@)?(?:(?:(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}(?:\\.(?:[0-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))|(?:(?:[a-z\\u00a1-\\uffff0-9]+-?)*[a-z\\u00a1-\\uffff0-9]+)(?:\\.(?:[a-z\\u00a1-\\uffff0-9]+-?)*[a-z\\u00a1-\\uffff0-9]+)*(?:\\.(?:[a-z\\u00a1-\\uffff]{2,})))|localhost)(?::\\d{2,5})?(?:(/|\\?|#)[^\\s]*)?$', 'i');
     return s.length < 2083 && r.test(s);
  },
  numDot: function(n) {
    _n = String(n)
    fun = function(s){
      if(s.length <= 3)
        return s
      return fun(s.slice(0,-3))+"."+s.slice(-3)
    }
    return fun((_n))
  },
  toDate: function (dat){
    var mils = Math.floor((0x10000 * dat.getUTCMilliseconds()) / 1000).toString(16)
    function pad(num, str){
      return ((new Array(num + 1)).join('0') + str).substr(-num,num)
    }
    return  '~' + dat.getUTCFullYear() +
            '.' + (dat.getUTCMonth() + 1) +
            '.' + dat.getUTCDate() +
           '..' + pad(2, dat.getUTCHours()) +
            '.' + pad(2, dat.getUTCMinutes()) +
            '.' + pad(2, dat.getUTCSeconds()) +
           '..' + pad(4, mils)
  },
  basepath: function(spur, pathname){
    spur = spur || ''
    pathname = pathname || window.location.pathname
    if(pathname[0] == '/') pathname = pathname.slice(1)
    pathname = pathname.split("/")

    var pref, pred, prec, base = ""
    while(base += "/"+(pref = pathname.shift()), pathname.length>0){
      if(pref[0] !== '~') break;
      if(pref === "~~") continue;
      base += "/"+(pred = pathname.shift())
      if(/[a-z\-]+/.test(pref.slice(1))){
        base += "/"+(prec = pathname.shift())
        if(prec == null) throw "Bad basepath."
        break;
      }
      if(pref !== "~") throw "Bad basepath /"+pref
      if(pred === "as"){
        base += "/"+(prec = pathname.shift())
        if(prec == null) throw "Bad basepath."
        continue;
      }
      throw "Bad basepath /~/"+pred
    }
    return base+spur
  }
}
<|endoftext|>


<commit_before><commit_msg>Psuedo private auth function<commit_after><|endoftext|>


<commit_before>var core = require('../../core');

/**
 * The DisplacementFilter class uses the pixel values from the specified texture (called the displacement map) to perform a displacement of an object.
 * You can use this filter to apply all manor of crazy warping effects
 * Currently the r property of the texture is used to offset the x and the g property of the texture is used to offset the y.
 *
 * @class
 * @extends AbstractFilter
 * @namespace PIXI
 * @param texture {Texture} The texture used for the displacement map * must be power of 2 texture at the moment
 */
function DisplacementFilter(sprite)
{
    var maskMatrix = new core.math.Matrix();
    sprite.renderable = false;

    //TODO move this code out to a frag and vert file.
    core.AbstractFilter.call(this,
        // vertex shader
        [
            'attribute vec2 aVertexPosition;',
            'attribute vec2 aTextureCoord;',
            'attribute vec4 aColor;',

            'uniform mat3 projectionMatrix;',
            'uniform mat3 otherMatrix;',

            'varying vec2 vMapCoord;',
            'varying vec2 vTextureCoord;',
            'varying vec4 vColor;',

            'void main(void)',
            '{',
            '   gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);',
            '   vTextureCoord = aTextureCoord;',
            '   vMapCoord = ( otherMatrix * vec3( aTextureCoord, 1.0)  ).xy;',
            '   vColor = vec4(aColor.rgb * aColor.a, aColor.a);',
            '}'
        ].join('\n'),
        // fragment shader
        [
            'precision lowp float;',

            'varying vec2 vMapCoord;',
            'varying vec2 vTextureCoord;',
            'varying vec4 vColor;',

            'uniform vec2 scale;',

            'uniform sampler2D uSampler;',
            'uniform sampler2D mapSampler;',

            'void main(void)',
            '{',
            '   vec4 original =  texture2D(uSampler, vTextureCoord);',
            '   vec4 map =  texture2D(mapSampler, vMapCoord);',
             '  map -= 0.5;',
            '   map.xy *= scale;',
            '   gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.x + map.x, vTextureCoord.y + map.y));',
            '}'
        ].join('\n'),
        // uniforms
        {
            mapSampler:     { type: 'sampler2D', value: sprite.texture },
            otherMatrix:    { type: 'mat3', value: maskMatrix.toArray(true) },
            scale:          { type: 'v2', value: { x: 0.01, y: 0.01 } }
        }
    );

    this.maskSprite = sprite;
    this.maskMatrix = maskMatrix;

    this.scale = new PIXI.Point(0,0);

}

DisplacementFilter.prototype = Object.create(core.AbstractFilter.prototype);
DisplacementFilter.prototype.constructor = DisplacementFilter;
module.exports = DisplacementFilter;

DisplacementFilter.prototype.applyFilter = function (renderer, input, output)
{
    var filterManager = renderer.filterManager;

    filterManager.calculateMappedMatrix(input.frame, this.maskSprite, this.maskMatrix);

    this.uniforms.otherMatrix.value = this.maskMatrix.toArray(true);
    this.uniforms.scale.value.x = this.scale.x * (1/input.frame.width);
    this.uniforms.scale.value.y = this.scale.y * (1/input.frame.height);

    var shader = this.getShader(renderer);
     // draw the filter...
    filterManager.applyFilter(shader, input, output);
};

Object.defineProperties(DisplacementFilter.prototype, {
    /**
     * The texture used for the displacement map. Must be power of 2 sized texture.
     *
     * @member {Texture}
     * @memberof DisplacementFilter#
     */
    map: {
        get: function ()
        {
            return this.uniforms.mapSampler.value;
        },
        set: function (value)
        {
            this.uniforms.mapSampler.value = value;

        }
    }
});
<commit_msg>Set default displacement to be 20,20<commit_after>var core = require('../../core');

/**
 * The DisplacementFilter class uses the pixel values from the specified texture (called the displacement map) to perform a displacement of an object.
 * You can use this filter to apply all manor of crazy warping effects
 * Currently the r property of the texture is used to offset the x and the g property of the texture is used to offset the y.
 *
 * @class
 * @extends AbstractFilter
 * @namespace PIXI
 * @param texture {Texture} The texture used for the displacement map * must be power of 2 texture at the moment
 */
function DisplacementFilter(sprite)
{
    var maskMatrix = new core.math.Matrix();
    sprite.renderable = false;

    //TODO move this code out to a frag and vert file.
    core.AbstractFilter.call(this,
        // vertex shader
        [
            'attribute vec2 aVertexPosition;',
            'attribute vec2 aTextureCoord;',
            'attribute vec4 aColor;',

            'uniform mat3 projectionMatrix;',
            'uniform mat3 otherMatrix;',

            'varying vec2 vMapCoord;',
            'varying vec2 vTextureCoord;',
            'varying vec4 vColor;',

            'void main(void)',
            '{',
            '   gl_Position = vec4((projectionMatrix * vec3(aVertexPosition, 1.0)).xy, 0.0, 1.0);',
            '   vTextureCoord = aTextureCoord;',
            '   vMapCoord = ( otherMatrix * vec3( aTextureCoord, 1.0)  ).xy;',
            '   vColor = vec4(aColor.rgb * aColor.a, aColor.a);',
            '}'
        ].join('\n'),
        // fragment shader
        [
            'precision lowp float;',

            'varying vec2 vMapCoord;',
            'varying vec2 vTextureCoord;',
            'varying vec4 vColor;',

            'uniform vec2 scale;',

            'uniform sampler2D uSampler;',
            'uniform sampler2D mapSampler;',

            'void main(void)',
            '{',
            '   vec4 original =  texture2D(uSampler, vTextureCoord);',
            '   vec4 map =  texture2D(mapSampler, vMapCoord);',
             '  map -= 0.5;',
            '   map.xy *= scale;',
            '   gl_FragColor = texture2D(uSampler, vec2(vTextureCoord.x + map.x, vTextureCoord.y + map.y));',
            '}'
        ].join('\n'),
        // uniforms
        {
            mapSampler:     { type: 'sampler2D', value: sprite.texture },
            otherMatrix:    { type: 'mat3', value: maskMatrix.toArray(true) },
            scale:          { type: 'v2', value: { x: 1, y: 1 } }
        }
    );

    this.maskSprite = sprite;
    this.maskMatrix = maskMatrix;

    this.scale = new PIXI.Point(20,20);

}

DisplacementFilter.prototype = Object.create(core.AbstractFilter.prototype);
DisplacementFilter.prototype.constructor = DisplacementFilter;
module.exports = DisplacementFilter;

DisplacementFilter.prototype.applyFilter = function (renderer, input, output)
{
    var filterManager = renderer.filterManager;

    filterManager.calculateMappedMatrix(input.frame, this.maskSprite, this.maskMatrix);

    this.uniforms.otherMatrix.value = this.maskMatrix.toArray(true);
    this.uniforms.scale.value.x = this.scale.x * (1/input.frame.width);
    this.uniforms.scale.value.y = this.scale.y * (1/input.frame.height);

    var shader = this.getShader(renderer);
     // draw the filter...
    filterManager.applyFilter(shader, input, output);
};

Object.defineProperties(DisplacementFilter.prototype, {
    /**
     * The texture used for the displacement map. Must be power of 2 sized texture.
     *
     * @member {Texture}
     * @memberof DisplacementFilter#
     */
    map: {
        get: function ()
        {
            return this.uniforms.mapSampler.value;
        },
        set: function (value)
        {
            this.uniforms.mapSampler.value = value;

        }
    }
});
<|endoftext|>


<commit_before>var core = require('../../core');
// @see https://github.com/substack/brfs/issues/25
var fs = require('fs');

/**
 * The DisplacementFilter class uses the pixel values from the specified texture (called the displacement map) to perform a displacement of an object.
 * You can use this filter to apply all manor of crazy warping effects
 * Currently the r property of the texture is used to offset the x and the g property of the texture is used to offset the y.
 *
 * @class
 * @extends AbstractFilter
 * @namespace PIXI
 * @param texture {Texture} The texture used for the displacement map * must be power of 2 texture at the moment
 */
function DisplacementFilter(sprite)
{
    var maskMatrix = new core.math.Matrix();
    sprite.renderable = false;

    core.AbstractFilter.call(this,
        // vertex shader
        fs.readFileSync(__dirname + '/displacement.vert', 'utf8'),
        // fragment shader
        fs.readFileSync(__dirname + '/displacement.frag', 'utf8'),
        // uniforms
        {
            mapSampler:     { type: 'sampler2D', value: sprite.texture },
            otherMatrix:    { type: 'mat3', value: maskMatrix.toArray(true) },
            scale:          { type: 'v2', value: { x: 1, y: 1 } }
        }
    );

    this.maskSprite = sprite;
    this.maskMatrix = maskMatrix;

    this.scale = new core.math.Point(20,20);

}

DisplacementFilter.prototype = Object.create(core.AbstractFilter.prototype);
DisplacementFilter.prototype.constructor = DisplacementFilter;
module.exports = DisplacementFilter;

DisplacementFilter.prototype.applyFilter = function (renderer, input, output)
{
    var filterManager = renderer.filterManager;

    filterManager.calculateMappedMatrix(input.frame, this.maskSprite, this.maskMatrix);

    this.uniforms.otherMatrix.value = this.maskMatrix.toArray(true);
    this.uniforms.scale.value.x = this.scale.x * (1/input.frame.width);
    this.uniforms.scale.value.y = this.scale.y * (1/input.frame.height);

    var shader = this.getShader(renderer);
     // draw the filter...
    filterManager.applyFilter(shader, input, output);
};

Object.defineProperties(DisplacementFilter.prototype, {
    /**
     * The texture used for the displacement map. Must be power of 2 sized texture.
     *
     * @member {Texture}
     * @memberof DisplacementFilter#
     */
    map: {
        get: function ()
        {
            return this.uniforms.mapSampler.value;
        },
        set: function (value)
        {
            this.uniforms.mapSampler.value = value;

        }
    }
});
<commit_msg>tweaked Displacement Map documentation<commit_after>var core = require('../../core');
// @see https://github.com/substack/brfs/issues/25
var fs = require('fs');

/**
 * The DisplacementFilter class uses the pixel values from the specified texture (called the displacement map) to perform a displacement of an object.
 * You can use this filter to apply all manor of crazy warping effects
 * Currently the r property of the texture is used to offset the x and the g property of the texture is used to offset the y.
 *
 * @class
 * @extends AbstractFilter
 * @namespace PIXI
 * @param sprite {Sprite} the sprite used for the displacement map. (make sure its added to the scene!)
 */
function DisplacementFilter(sprite)
{
    var maskMatrix = new core.math.Matrix();
    sprite.renderable = false;

    core.AbstractFilter.call(this,
        // vertex shader
        fs.readFileSync(__dirname + '/displacement.vert', 'utf8'),
        // fragment shader
        fs.readFileSync(__dirname + '/displacement.frag', 'utf8'),
        // uniforms
        {
            mapSampler:     { type: 'sampler2D', value: sprite.texture },
            otherMatrix:    { type: 'mat3', value: maskMatrix.toArray(true) },
            scale:          { type: 'v2', value: { x: 1, y: 1 } }
        }
    );

    this.maskSprite = sprite;
    this.maskMatrix = maskMatrix;

    this.scale = new core.math.Point(20,20);

}

DisplacementFilter.prototype = Object.create(core.AbstractFilter.prototype);
DisplacementFilter.prototype.constructor = DisplacementFilter;
module.exports = DisplacementFilter;

DisplacementFilter.prototype.applyFilter = function (renderer, input, output)
{
    var filterManager = renderer.filterManager;

    filterManager.calculateMappedMatrix(input.frame, this.maskSprite, this.maskMatrix);

    this.uniforms.otherMatrix.value = this.maskMatrix.toArray(true);
    this.uniforms.scale.value.x = this.scale.x * (1/input.frame.width);
    this.uniforms.scale.value.y = this.scale.y * (1/input.frame.height);

    var shader = this.getShader(renderer);
     // draw the filter...
    filterManager.applyFilter(shader, input, output);
};

Object.defineProperties(DisplacementFilter.prototype, {
    /**
     * The texture used for the displacement map. Must be power of 2 sized texture.
     *
     * @member {Texture}
     * @memberof DisplacementFilter#
     */
    map: {
        get: function ()
        {
            return this.uniforms.mapSampler.value;
        },
        set: function (value)
        {
            this.uniforms.mapSampler.value = value;

        }
    }
});
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2020 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.nanos.fs.fileDropZone',
  name: 'FileDropZone',
  extends: 'foam.u2.Controller',

  documentation: 'A default zone to drag & drop files',

  requires: [
    'foam.log.LogLevel',
    'foam.blob.BlobBlob',
    'foam.nanos.fs.File',
    'foam.nanos.fs.FileArray'
  ],

  imports: [
    'ctrl',
    'user',
    'fileTypeDAO'
  ],

  exports: [
    'allowRemoval',
    'removeFile'
  ],

  css: `
    ^ {
      box-sizing: border-box;
      padding: 16px;
      border: 2px dashed #8e9090;
      border-radius: 3px;
      box-shadow: inset 0 1px 2px 0 rgba(116, 122, 130, 0.21);
    }
    ^instruction-container {
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
      text-align: center;
      height: 228px;
    }
    ^instruction-container.selection {
      height: 172px;
      margin-bottom: 16px;
    }
    ^input {
      display: none;
    }
    ^title {
      font-size: 16px;
      font-weight: 900;
      margin: 0;
    }
    ^or {
      display: inline-block;
      vertical-align: bottom;
      margin: 0;
      margin-top: 8px;
    }
    ^link {
      display: inline-block;
      cursor: pointer;
      color: /*%PRIMARY3%*/ #406dea;
      margin: 0;
      margin-left: 5px;
    }
    ^caption-container {
      margin-top: 24px;
      position: relative;
      top: 60px;
    }
    ^caption {
      display: inline-block;
      font-size: 10px;
      color: #525455;
      margin: 0;
      margin-top: 4px;
    }
  `,

  messages: [
    { name: 'LABEL_DEFAULT_TITLE', message: 'DRAG & DROP YOUR FILE HERE' },
    { name: 'LABEL_OR', message: 'or' },
    { name: 'LABEL_BROWSE', message: 'browse' },
    { name: 'LABEL_SUPPORTED', message: 'Supported file types:' },
    { name: 'LABEL_MAX_SIZE', message: 'Max Size:' },
    { name: 'ERROR_FILE_TYPE', message: 'Invalid file type' },
    { name: 'ERROR_FILE_SIZE', message: 'File size exceeds 15MB' }
  ],

  properties: [
    {
      class: 'String',
      name: 'title'
    },
    {
      name: 'supportedFormats',
      documentation: `Please use the following format: { 'image/jpg' : 'JPG' }`,
      value: {}
    },
    {
      class: 'Boolean',
      name: 'isMultipleFiles',
      value: true
    },
    {
      class: 'Boolean',
      name: 'allowRemoval',
      value: true
    },
    {
      class: 'foam.nanos.fs.FileArray',
      name: 'files',
      factory: function() {
        return [];
      }
    },
    {
      class: 'Long',
      name: 'maxSize',
      value: 15,
      documentation: 'Dictates maximum file size in MB (Megabyte).'
    },
    {
      name: 'onFilesChanged',
      documentation: 'When a file has been selected/changed/removed, this function will be called. (OPTIONAL)'
    }
  ],

  methods: [
    async function initE() {
      this.SUPER();
      var self = this;

      if ( Object.keys(this.supportedFormats).length == 0 ) {
        let s = await this.fileTypeDAO.select()
        s.array.forEach(type => {
          this.supportedFormats[type.toSummary()] = type.abbreviation
        })
      }

      this
        .addClass(this.myClass())
        .start().addClass(this.myClass('instruction-container')).enableClass('selection', this.files$.map((v) => { return v.length > 0; }))
          .start('p').addClass(this.myClass('title')).add(this.title || this.LABEL_DEFAULT_TITLE).end()
          .start().addClass(this.myClass('browse-container'))
            .start('p').addClass(this.myClass('or')).add(this.LABEL_OR).end()
            .start('p').addClass(this.myClass('link'))
              .add(this.LABEL_BROWSE)
              .on('click', this.onAddAttachmentClicked)
            .end()
          .end()
          .start().addClass(this.myClass('caption-container')).hide(this.files$.map((v) => { return v.length > 0; }))
            .start()
              .start('p').addClass(this.myClass('caption')).add(this.LABEL_SUPPORTED).end()
              .start('p').addClass(self.myClass('caption')).add(this.getSupportedTypes(true)).end()
            .end()
            .start()
              .start('p').addClass(this.myClass('caption')).add(this.LABEL_MAX_SIZE + ' ' + this.maxSize + 'MB').end()
            .end()
          .end()
        .end()
        .add(this.slot(function(files) {
          var e = this.E();
          for ( var i = 0; i < files.length; i++ ) {
            e.tag({
              class: 'foam.nanos.fs.fileDropZone.FileCard',
              data: files[i],
              index: i
            });
          }
          return e;
        }, this.files$))
        .on('drop', this.onDrop)
        .callIf(this.isMultipleFiles, function() {
          this.start('input').addClass(this.myClass('input'))
            .attrs({
              type: 'file',
              accept: this.getSupportedTypes(),
              multiple: 'multiple'
            })
            .on('change', this.onChange)
          .end();
        })
        .callIf(! this.isMultipleFiles, function() {
          this.start('input').addClass(this.myClass('input'))
            .attrs({
              type: 'file',
              accept: this.getSupportedTypes()
            })
            .on('change', this.onChange)
          .end();
        });
    },

    function getSupportedTypes(readable) {
      var supportedTypes = Object.keys(this.supportedFormats);
      var constructedString = '';

      if ( readable ) {
        supportedTypes.forEach((type, index) => {
          constructedString += this.supportedFormats[type];
          if ( index < supportedTypes.length - 1 ) {
            constructedString += ', ';
          }
        });
      } else {
        supportedTypes.forEach((type, index) => {
          constructedString += type;
          if ( index < supportedTypes.length - 1 ) {
            constructedString += ', ';
          }
        });
      }
      return constructedString;
    },

    function addFiles(files) {
      var errors = false;
      for ( var i = 0; i < files.length; i++ ) {
        // skip files that exceed limit
        if ( files[i].size > ( this.maxSize * 1024 * 1024 ) ) {
          if ( ! errors ) errors = true;
          ctrl.notify(this.ERROR_FILE_SIZE, '', this.LogLevel.ERROR, true);
          continue;
        }
        var isIncluded = false;
        for ( var j = 0; j < this.files.length; j++ ) {
          if ( this.files[j].filename.localeCompare(files[i].name) === 0 ) {
            isIncluded = true;
            break;
          }
        }
        if ( isIncluded ) continue;
        if ( this.isMultipleFiles ) {
          this.files.push(this.File.create({
            owner: this.user.id,
            filename: files[i].name,
            filesize: files[i].size,
            mimeType: files[i].type,
            data: this.BlobBlob.create({
              blob: files[i]
            })
          }));
        } else {
          this.files[0] = this.File.create({
            owner: this.user.id,
            filename: files[i].name,
            filesize: files[i].size,
            mimeType: files[i].type,
            data: this.BlobBlob.create({
              blob: files[i]
            })
          });
        }
      }
      this.files = Array.from(this.files);
    },

    function isFileType(file) {
      return ( file.type in this.supportedFormats );
    },

    function removeFile(atIndex) {
      var files = Array.from(this.files);
      files.splice(atIndex, 1);
      this.files = files;
      this.document.querySelector('.' + this.myClass('input')).value = null;
    }
  ],

  listeners: [
    function onAddAttachmentClicked(e) {
      if ( typeof e.target != 'undefined' ) {
        if ( e.target.tagName == 'P' && e.target.tagName != 'A' ) {
          this.document.querySelector('.' + this.myClass('input')).click();
        }
      } else {
        // For IE browser
        if ( e.srcElement.tagName == 'P' && e.srcElement.tagName != 'A' ) {
          this.document.querySelector('.' + this.myClass('input')).click();
        }
      }
    },

    function onDrop(e) {
      e.preventDefault();
      var files = [];
      var inputFile;
      if ( e.dataTransfer.items ) {
        inputFile = e.dataTransfer.items;
        if ( inputFile ) {
          for ( var i = 0; i < inputFile.length; i++ ) {
            // If dropped items aren't files, reject them
            if ( inputFile[i].kind === 'file' ) {
              var file = inputFile[i].getAsFile();
              if ( this.isFileType(file) ) {
                files.push(file);
              } else {
                ctrl.notify(this.ERROR_FILE_TYPE, '', this.LogLevel.ERROR, true);
              }
            }
          }
        }
      } else if ( e.dataTransfer.files ) {
        inputFile = e.dataTransfer.files;
        for ( var i = 0; i < inputFile.length; i++ ) {
          var file = inputFile[i];
          if ( this.isFileType(file) ) files.push(file);
          else {
            ctrl.notify(this.ERROR_FILE_TYPE, '', this.LogLevel.ERROR, true);
          }
        }
      }
      this.addFiles(files);
    },

    function onChange(e) {
      var files = e.target.files;
      this.addFiles(files);
      // Remove all temporary files in the element.target.files
      this.document.querySelector('.' + this.myClass('input')).value = null;
    }
  ]
});
<commit_msg>Formatting.<commit_after>/**
 * @license
 * Copyright 2020 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.nanos.fs.fileDropZone',
  name: 'FileDropZone',
  extends: 'foam.u2.Controller',

  documentation: 'A default zone to drag & drop files',

  requires: [
    'foam.log.LogLevel',
    'foam.blob.BlobBlob',
    'foam.nanos.fs.File',
    'foam.nanos.fs.FileArray'
  ],

  imports: [
    'ctrl',
    'user',
    'fileTypeDAO'
  ],

  exports: [
    'allowRemoval',
    'removeFile'
  ],

  css: `
    ^ {
      box-sizing: border-box;
      padding: 16px;
      border: 2px dashed #8e9090;
      border-radius: 3px;
      box-shadow: inset 0 1px 2px 0 rgba(116, 122, 130, 0.21);
    }
    ^instruction-container {
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
      text-align: center;
      height: 228px;
    }
    ^instruction-container.selection {
      height: 172px;
      margin-bottom: 16px;
    }
    ^input {
      display: none;
    }
    ^title {
      font-size: 16px;
      font-weight: 900;
      margin: 0;
    }
    ^or {
      display: inline-block;
      vertical-align: bottom;
      margin: 0;
      margin-top: 8px;
    }
    ^link {
      display: inline-block;
      cursor: pointer;
      color: /*%PRIMARY3%*/ #406dea;
      margin: 0;
      margin-left: 5px;
    }
    ^caption-container {
      margin-top: 24px;
      position: relative;
      top: 60px;
    }
    ^caption {
      display: inline-block;
      font-size: 10px;
      color: #525455;
      margin: 0;
      margin-top: 4px;
    }
  `,

  messages: [
    { name: 'LABEL_DEFAULT_TITLE', message: 'DRAG & DROP YOUR FILE HERE' },
    { name: 'LABEL_OR',            message: 'or' },
    { name: 'LABEL_BROWSE',        message: 'browse' },
    { name: 'LABEL_SUPPORTED',     message: 'Supported file types:' },
    { name: 'LABEL_MAX_SIZE',      message: 'Max Size:' },
    { name: 'ERROR_FILE_TYPE',     message: 'Invalid file type' },
    { name: 'ERROR_FILE_SIZE',     message: 'File size exceeds 15MB' }
  ],

  properties: [
    {
      class: 'String',
      name: 'title'
    },
    {
      name: 'supportedFormats',
      documentation: `Please use the following format: { 'image/jpg' : 'JPG' }`,
      value: {}
    },
    {
      class: 'Boolean',
      name: 'isMultipleFiles',
      value: true
    },
    {
      class: 'Boolean',
      name: 'allowRemoval',
      value: true
    },
    {
      class: 'foam.nanos.fs.FileArray',
      name: 'files',
      factory: function() {
        return [];
      }
    },
    {
      class: 'Long',
      name: 'maxSize',
      value: 15,
      documentation: 'Dictates maximum file size in MB (Megabyte).'
    },
    {
      name: 'onFilesChanged',
      documentation: 'When a file has been selected/changed/removed, this function will be called. (OPTIONAL)'
    }
  ],

  methods: [
    async function initE() {
      this.SUPER();
      var self = this;

      if ( Object.keys(this.supportedFormats).length == 0 ) {
        let s = await this.fileTypeDAO.select()
        s.array.forEach(type => {
          this.supportedFormats[type.toSummary()] = type.abbreviation
        })
      }

      this
        .addClass(this.myClass())
        .start().addClass(this.myClass('instruction-container')).enableClass('selection', this.files$.map((v) => { return v.length > 0; }))
          .start('p').addClass(this.myClass('title')).add(this.title || this.LABEL_DEFAULT_TITLE).end()
          .start().addClass(this.myClass('browse-container'))
            .start('p').addClass(this.myClass('or')).add(this.LABEL_OR).end()
            .start('p').addClass(this.myClass('link'))
              .add(this.LABEL_BROWSE)
              .on('click', this.onAddAttachmentClicked)
            .end()
          .end()
          .start().addClass(this.myClass('caption-container')).hide(this.files$.map((v) => { return v.length > 0; }))
            .start()
              .start('p').addClass(this.myClass('caption')).add(this.LABEL_SUPPORTED).end()
              .start('p').addClass(self.myClass('caption')).add(this.getSupportedTypes(true)).end()
            .end()
            .start()
              .start('p').addClass(this.myClass('caption')).add(this.LABEL_MAX_SIZE + ' ' + this.maxSize + 'MB').end()
            .end()
          .end()
        .end()
        .add(this.slot(function(files) {
          var e = this.E();
          for ( var i = 0; i < files.length; i++ ) {
            e.tag({
              class: 'foam.nanos.fs.fileDropZone.FileCard',
              data: files[i],
              index: i
            });
          }
          return e;
        }, this.files$))
        .on('drop', this.onDrop)
        .callIf(this.isMultipleFiles, function() {
          this.start('input').addClass(this.myClass('input'))
            .attrs({
              type: 'file',
              accept: this.getSupportedTypes(),
              multiple: 'multiple'
            })
            .on('change', this.onChange)
          .end();
        })
        .callIf(! this.isMultipleFiles, function() {
          this.start('input').addClass(this.myClass('input'))
            .attrs({
              type: 'file',
              accept: this.getSupportedTypes()
            })
            .on('change', this.onChange)
          .end();
        });
    },

    function getSupportedTypes(readable) {
      var supportedTypes = Object.keys(this.supportedFormats);
      var constructedString = '';

      if ( readable ) {
        supportedTypes.forEach((type, index) => {
          constructedString += this.supportedFormats[type];
          if ( index < supportedTypes.length - 1 ) {
            constructedString += ', ';
          }
        });
      } else {
        supportedTypes.forEach((type, index) => {
          constructedString += type;
          if ( index < supportedTypes.length - 1 ) {
            constructedString += ', ';
          }
        });
      }
      return constructedString;
    },

    function addFiles(files) {
      var errors = false;
      for ( var i = 0; i < files.length; i++ ) {
        // skip files that exceed limit
        if ( files[i].size > ( this.maxSize * 1024 * 1024 ) ) {
          if ( ! errors ) errors = true;
          ctrl.notify(this.ERROR_FILE_SIZE, '', this.LogLevel.ERROR, true);
          continue;
        }
        var isIncluded = false;
        for ( var j = 0; j < this.files.length; j++ ) {
          if ( this.files[j].filename.localeCompare(files[i].name) === 0 ) {
            isIncluded = true;
            break;
          }
        }
        if ( isIncluded ) continue;
        if ( this.isMultipleFiles ) {
          this.files.push(this.File.create({
            owner:    this.user.id,
            filename: files[i].name,
            filesize: files[i].size,
            mimeType: files[i].type,
            data:     this.BlobBlob.create({
              blob: files[i]
            })
          }));
        } else {
          this.files[0] = this.File.create({
            owner:    this.user.id,
            filename: files[i].name,
            filesize: files[i].size,
            mimeType: files[i].type,
            data:     this.BlobBlob.create({
              blob: files[i]
            })
          });
        }
      }
      this.files = Array.from(this.files);
    },

    function isFileType(file) {
      return ( file.type in this.supportedFormats );
    },

    function removeFile(atIndex) {
      var files = Array.from(this.files);
      files.splice(atIndex, 1);
      this.files = files;
      this.document.querySelector('.' + this.myClass('input')).value = null;
    }
  ],

  listeners: [
    function onAddAttachmentClicked(e) {
      if ( typeof e.target != 'undefined' ) {
        if ( e.target.tagName == 'P' && e.target.tagName != 'A' ) {
          this.document.querySelector('.' + this.myClass('input')).click();
        }
      } else {
        // For IE browser
        if ( e.srcElement.tagName == 'P' && e.srcElement.tagName != 'A' ) {
          this.document.querySelector('.' + this.myClass('input')).click();
        }
      }
    },

    function onDrop(e) {
      e.preventDefault();
      var files = [];
      var inputFile;
      if ( e.dataTransfer.items ) {
        inputFile = e.dataTransfer.items;
        if ( inputFile ) {
          for ( var i = 0 ; i < inputFile.length ; i++ ) {
            // If dropped items aren't files, reject them
            if ( inputFile[i].kind === 'file' ) {
              var file = inputFile[i].getAsFile();
              if ( this.isFileType(file) ) {
                files.push(file);
              } else {
                ctrl.notify(this.ERROR_FILE_TYPE, '', this.LogLevel.ERROR, true);
              }
            }
          }
        }
      } else if ( e.dataTransfer.files ) {
        inputFile = e.dataTransfer.files;
        for ( var i = 0 ; i < inputFile.length ; i++ ) {
          var file = inputFile[i];
          if ( this.isFileType(file) ) {
            files.push(file);
          } else {
            ctrl.notify(this.ERROR_FILE_TYPE, '', this.LogLevel.ERROR, true);
          }
        }
      }
      this.addFiles(files);
    },

    function onChange(e) {
      var files = e.target.files;
      this.addFiles(files);
      // Remove all temporary files in the element.target.files
      this.document.querySelector('.' + this.myClass('input')).value = null;
    }
  ]
});
<|endoftext|>