diff --git a/.gitignore b/.gitignore
index 3a51f4a..03dd994 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,7 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.
# ignore static files generated during build process
/static/app.html.fragment
+/static/static.html.fragment
/static/blogFeed.json
# deploy key
diff --git a/.travis.yml b/.travis.yml
index f53b5f8..f9b97dc 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,6 +1,6 @@
language: node_js
node_js:
-- 6.3.1
+- 7.9.0
before_script:
- npm install -g grunt-cli
script: grunt package
diff --git a/Gruntfile.js b/Gruntfile.js
index ecc3736..c93eda3 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -5,6 +5,8 @@ var moment = require('moment');
var fs = require('fs');
var grameneRelease = require('./package.json').gramene.dbRelease;
+var reactomeURL = require('./package.json').gramene.reactomeURL;
+
var webserviceVersion = 'v' + grameneRelease;
module.exports = function (grunt) {
@@ -29,11 +31,11 @@ module.exports = function (grunt) {
},
exec: {
- generateStaticApp: {
- cmd: 'node scripts/babel.js'
+ generateStaticWelcome: {
+ cmd: 'node scripts/babel.js -c app'
},
- blogFeed: {
- cmd: 'node scripts/getBlogFeed.js'
+ generateStaticApp: {
+ cmd: 'node scripts/babel.js -c static'
}
},
@@ -96,7 +98,7 @@ module.exports = function (grunt) {
},
html: {
files: ['*.template.html'],
- tasks: ['packageIndexHtml']
+ tasks: ['packageIndexHtml','packageStaticHtml']
},
styles: {
files: ['styles/*.less'],
@@ -121,7 +123,7 @@ module.exports = function (grunt) {
{expand: true, cwd: 'assets/', src: ['**'], dest: 'build/assets/'},
{
expand: true,
- cwd: 'node_modules/gramene-dalliance',
+ cwd: 'node_modules/dalliance',
src: [
'css/*-scoped.css',
'css/font-awesome.min.css',
@@ -131,8 +133,12 @@ module.exports = function (grunt) {
dest: 'build/assets/gramene-dalliance/'
},
{
- src: 'node_modules/gramene-dalliance/prebuilt/worker-all.js',
+ src: 'node_modules/dalliance/prebuilt/worker-all.js',
dest: 'build/assets/gramene-dalliance/worker-all.js'
+ },
+ {
+ src: 'node_modules/dalliance/prebuilt/dalliance-all.js',
+ dest: 'build/assets/gramene-dalliance/dalliance-all.js'
}
]
},
@@ -195,7 +201,8 @@ module.exports = function (grunt) {
footer: footer,
content: content,
loadingMessage: loadingMessage,
- hideIntro: hideIntro
+ hideIntro: hideIntro,
+ reactomeURL: reactomeURL
};
return template(props);
@@ -205,9 +212,77 @@ module.exports = function (grunt) {
var atlas = grunt.file.read('./static/atlasWidget.template.html')
grunt.file.write('build/atlasWidget.html', atlas);
+
+ // var htaccess = "RewriteBase /\n"
+ // + "RewriteRule ^index\.html$ - [L]\n"
+ // + "RewriteCond %{REQUEST_FILENAME} !-f\n"
+ // + "RewriteCond %{REQUEST_FILENAME} !-d\n"
+ // + "RewriteRule . /index.html [L]\n";
+ // grunt.file.write('build/.htaccess', htaccess);
});
-
- grunt.registerTask('generateStaticFiles', ['exec:blogFeed', 'copy:assets', 'copy:icons', 'exec:generateStaticApp', 'packageIndexHtml']);
+
+ grunt.registerTask('packageStaticHtml', 'Build static.html for distribution.', function () {
+ var footer = (function compileFooterTemplate() {
+ function defaultServer() {
+ const PROD_SERVER = 'http://data.gramene.org/';
+ const DEV_SERVER = 'http://devdata.gramene.org/';
+ var defaultServer;
+
+ if (process.env.GRAMENE_SERVER) {
+ defaultServer = process.env.GRAMENE_SERVER;
+ }
+ else if (props.tag || props.branch === 'master') {
+ defaultServer = PROD_SERVER;
+ }
+ else {
+ defaultServer = DEV_SERVER;
+ }
+
+ defaultServer += webserviceVersion + '/swagger';
+
+ return defaultServer;
+ }
+
+ var template = _.template(grunt.file.read('./static/footer.template.html'));
+
+ var props = {
+ jobId: process.env.TRAVIS_JOB_ID,
+ jobNumber: process.env.TRAVIS_JOB_NUMBER,
+ branch: process.env.TRAVIS_BRANCH,
+ tag: process.env.TRAVIS_TAG,
+ user: process.env.USER,
+ date: moment().format('MMMM Do YYYY [at] h:mm:ss a'),
+ isDev: process.env.isDev,
+ grameneRelease: grameneRelease
+ };
+
+ props.defaultServer = defaultServer();
+ console.log("This build will use " + props.defaultServer + " as default web service server");
+
+ return template(props);
+ })();
+
+ var index = (function compileIndexTemplate() {
+ var template = _.template(grunt.file.read('./static/index.template.html'));
+ var content = grunt.file.read('./static/static.html.fragment');
+ var loadingMessage = grunt.file.read('./static/loading-message.html.fragment');
+ var hideIntro = grunt.file.read('./static/hide-intro.html.fragment');
+
+ var props = {
+ footer: footer,
+ content: content,
+ loadingMessage: loadingMessage,
+ hideIntro: hideIntro,
+ reactomeURL: reactomeURL
+ };
+
+ return template(props);
+ })();
+
+ grunt.file.write('build/static.html', index);
+ });
+
+ grunt.registerTask('generateStaticFiles', ['copy:assets', 'copy:icons', 'exec:generateStaticApp', 'exec:generateStaticWelcome', 'packageStaticHtml', 'packageIndexHtml']);
grunt.registerTask('test', ['jasmine_node']);
grunt.registerTask('default', ['env:dev', 'generateStaticFiles', 'less:dev', 'browserify:dev', 'watch']);
grunt.registerTask('package', ['env:prod', 'generateStaticFiles', 'less:production', 'browserify:production', 'test']);
diff --git a/assets/images/welcome/TrackHub.png b/assets/images/welcome/TrackHub.png
new file mode 100644
index 0000000..39abd85
Binary files /dev/null and b/assets/images/welcome/TrackHub.png differ
diff --git a/assets/images/welcome/ensemblgramene.png b/assets/images/welcome/ensemblgramene.png
new file mode 100644
index 0000000..28c68af
Binary files /dev/null and b/assets/images/welcome/ensemblgramene.png differ
diff --git a/assets/images/welcome/plantReactome.svg b/assets/images/welcome/plantReactome.svg
new file mode 100644
index 0000000..8422f10
--- /dev/null
+++ b/assets/images/welcome/plantReactome.svg
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/assets/images/welcome/tools.png b/assets/images/welcome/tools.png
new file mode 100644
index 0000000..7a10cfd
Binary files /dev/null and b/assets/images/welcome/tools.png differ
diff --git a/package.json b/package.json
index 4b0fa68..901521f 100644
--- a/package.json
+++ b/package.json
@@ -1,10 +1,12 @@
{
"name": "gramoogle",
- "version": "2.5.1",
+ "version": "3.0.0",
"description": "Gramene search interface",
"main": "index.js",
"gramene": {
- "dbRelease": 52
+ "dbRelease": 53,
+ "reactomeURL": "//plantreactome.gramene.org",
+ "ensemblURL": "//ensembl.gramene.org"
},
"scripts": {
"build": "grunt package",
@@ -23,25 +25,44 @@
"homepage": "https://github.com/warelab/gramoogle",
"dependencies": {
"axios": "^0.9.1",
+ "body-parser": "^1.16.0",
+ "component-closest": "^1.0.1",
+ "compression": "^1.6.2",
+ "cors": "^2.8.1",
+ "dalliance": "git://github.com/ajo2995/dalliance.git#cran",
+ "email": "^0.2.6",
+ "express": "^4.14.0",
+ "flat-to-nested": "^1.0.2",
"gramene-bins-client": "^2.2.9",
"gramene-client-cache": "1.0.0",
- "gramene-dalliance": "^0.13.2",
"gramene-dbxrefs": "^3.0.2",
- "gramene-genetree-vis": "3.1.4",
+ "gramene-genetree-vis": "git://github.com/warelab/gramene-genetree-vis.git",
"gramene-search-client": "^3.0.4",
"gramene-search-vis": "^4.1.2",
"gramene-taxonomy-with-genomes": "^3.0.8",
- "gramene-trees-client": "2.4.7",
+ "gramene-trees-client": "git://github.com/warelab/gramene-trees-client.git#consensus",
+ "history": "^4.5.1",
+ "is-email": "^1.0.0",
"lodash": "^4.3.0",
+ "mysql": "^2.13.0",
+ "node-schedule": "^1.2.0",
"normalize.less": "^1.0.0",
"numeral": "^1.5.3",
+ "path": "^0.12.7",
"q": "^1.2.0",
"query-string": "^4.2.2",
- "react": "^15.0.1",
- "react-bootstrap": "^0.30.0",
- "react-dom": "^15.0.1",
+ "react": "^15.4.2",
+ "react-bootstrap": "^0.30.7",
+ "react-dom": "^15.4.2",
+ "react-ga": "^2.2.0",
+ "react-recaptcha": "^2.2.6",
+ "react-router": "^3.0.2",
+ "react-tree-menu": "^1.5.0",
+ "reactify": "^1.1.1",
"reflux": "^0.4.1",
"rss-parser": "latest",
+ "soap": "^0.18.0",
+ "tree-model": "^1.0.6",
"xml2js": "^0.4.16"
},
"devDependencies": {
diff --git a/scripts/actions/docActions.js b/scripts/actions/docActions.js
index f93f831..cd35c41 100644
--- a/scripts/actions/docActions.js
+++ b/scripts/actions/docActions.js
@@ -30,7 +30,7 @@ function getClientPromise(collection) {
return client[collection];
}
-DocActions.needDocs.listen(function (collection, id, postprocessFn) {
+DocActions.needDocs.listen(function (collection, id, postprocessFn, callbackFn) {
var cacheKey, clientCall;
cacheKey = [collection,id];
console.log('DocActions.needDocs', collection, id);
@@ -43,7 +43,13 @@ DocActions.needDocs.listen(function (collection, id, postprocessFn) {
var promise, cached;
if ((cached = docCache.get(cacheKey))) {
- promise = Q(cached);
+ promise = Q(cached)
+ .then(function callbackMaybe(result) {
+ if (callbackFn) {
+ callbackFn(result.docs);
+ }
+ return result;
+ });
}
else {
docCache.set(id, 'loading…');
@@ -71,6 +77,12 @@ DocActions.needDocs.listen(function (collection, id, postprocessFn) {
}
return result;
})
+ .then(function callbackMaybe(result) {
+ if (callbackFn) {
+ callbackFn(result.docs);
+ }
+ return result;
+ })
.then(function addToCache(result) {
docCache.set(cacheKey, result);
return result;
diff --git a/scripts/actions/drupalActions.js b/scripts/actions/drupalActions.js
new file mode 100644
index 0000000..d705c23
--- /dev/null
+++ b/scripts/actions/drupalActions.js
@@ -0,0 +1,26 @@
+import Reflux from "reflux";
+import {getBlogFeed} from "../welcome/getDrupalContent.js";
+// import Q from 'q';
+
+const DrupalActions = Reflux.createActions([
+ {'refreshBlogFeed': {asyncResult: true}},
+ // {'fetchDrupalPage': {asyncResult: true}}
+]);
+
+// console.log(DrupalActions);
+
+DrupalActions.refreshBlogFeed.listen(function () {
+ console.log("Will refreshBlog Feed");
+ getBlogFeed()
+ .then(this.completed)
+ .catch(this.failed);
+});
+
+// DrupalActions.fetchDrupalPage.listen(function (url) {
+// console.log("Will fetchDrupalPage from", url);
+// getDrupalPage(url)
+// .then(this.completed)
+// .catch(this.failed);
+// });
+
+export default DrupalActions;
\ No newline at end of file
diff --git a/scripts/actions/welcomeActions.js b/scripts/actions/welcomeActions.js
index 52ccb79..9c8d1e4 100644
--- a/scripts/actions/welcomeActions.js
+++ b/scripts/actions/welcomeActions.js
@@ -1,21 +1,12 @@
import Reflux from "reflux";
-import getBlogFeed from "../welcome/getBlogFeed.js";
import Q from 'q';
const WelcomeActions = Reflux.createActions([
- {'refreshBlogFeed': {asyncResult: true}},
{'flashSearchBox': {asyncResult: true}}
]);
console.log(WelcomeActions);
-WelcomeActions.refreshBlogFeed.listen(function () {
- console.log("Will refreshBlog Feed");
- getBlogFeed()
- .then(this.completed)
- .catch(this.failed);
-});
-
WelcomeActions.flashSearchBox.listen(function (delayMs = 500) {
const deferred = Q.defer();
setTimeout(()=>deferred.resolve("finish"), delayMs);
diff --git a/scripts/babel.js b/scripts/babel.js
index 2e6e1f3..a7ef933 100644
--- a/scripts/babel.js
+++ b/scripts/babel.js
@@ -3,6 +3,9 @@
'use strict';
var fs = require('fs');
+var argv = require('minimist')(process.argv.slice(2));
+
+var serverRenderMode = argv.c;
require('babel-register')({
presets: ['es2015', 'react']
@@ -16,4 +19,4 @@ var ReactDOMServer = require('react-dom/server');
var App = React.createFactory(require('./components/appStatic.jsx').default);
-fs.writeFileSync('static/app.html.fragment', ReactDOMServer.renderToString(new App({context: 'compile'})));
\ No newline at end of file
+fs.writeFileSync(`static/${serverRenderMode}.html.fragment`, ReactDOMServer.renderToString(new App({serverRenderMode})));
\ No newline at end of file
diff --git a/scripts/components/DrupalPage.jsx b/scripts/components/DrupalPage.jsx
new file mode 100644
index 0000000..6d3496f
--- /dev/null
+++ b/scripts/components/DrupalPage.jsx
@@ -0,0 +1,120 @@
+import React from 'react';
+import axios from "axios";
+import { browserHistory } from 'react-router';
+import _ from 'lodash';
+import closest from 'component-closest';
+import ReactGA from "react-ga";
+
+
+export default class DrupalPage extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ aliases: {}
+ };
+ }
+
+ componentWillMount() {
+ // populate this.state.aliases
+ this.fetchAliases();
+ }
+
+ getNid(params) {
+ if (params.drupalPath) {
+ if (this.state.aliases && this.state.aliases[params.drupalPath]) {
+ return this.state.aliases[params.drupalPath];
+ }
+ }
+ else if (params.drupalNode) {
+ return params.drupalNode;
+ }
+ return null;
+ }
+
+ componentDidMount() {
+ window.scrollTo(0, 0);
+ }
+
+ componentDidUpdate(prevProps, prevState) {
+ window.scrollTo(0, 0);
+ }
+
+ initListener() {
+ let iframeDoc = this.iframe.contentDocument || this.iframe.contentWindow.document;
+ if (typeof iframeDoc.addEventListener !== "undefined") {
+ iframeDoc.addEventListener("click", this.iframeClickHandler.bind(this), true);
+ } else if (typeof iframeDoc.attachEvent !== "undefined") {
+ iframeDoc.attachEvent("onclick", this.iframeClickHandler.bind(this));
+ }
+ }
+
+ componentWillReceiveProps(nextProps) {
+ let nextNid = this.getNid(nextProps.params);
+ if (nextNid && this.nid !== nextNid) {
+ this.nid = nextNid;
+ this.iframe.src = `/ww/${this.nid}`;
+ }
+ }
+
+ iframeClickHandler(e) {
+ let target = e.target || e.srcElement;
+ target = closest(target, 'a');
+ let href = target.getAttribute('href');
+ let drupalLink;
+ let matches = href.match(/(node\/\d+)$/);
+ if (!matches) {
+ matches = href.match(/gramene\.org\/(.+)/);
+ if (matches && this.state.aliases[matches[1]]) {
+ drupalLink = matches[1];
+ }
+ }
+ else {
+ drupalLink = matches[1];
+ }
+ if (drupalLink) {
+ e.preventDefault();
+ browserHistory.push('/'+drupalLink);
+ }
+ else {
+ ReactGA.outboundLink({
+ label: href
+ }, function () {
+ console.log('have fun at',href);
+ });
+ }
+ }
+
+ componentWillUnmount () {
+ this.ignoreLastFetch = true;
+ }
+
+ fetchAliases () {
+ let url = `/aliases`;
+ axios.get(url).then(response => {
+ if (!this.ignoreLastFetch) {
+ this.setState({ aliases: response.data });
+ }
+ })
+ }
+
+ render() {
+ let src;
+ this.nid = this.getNid(this.props.params);
+ if (this.nid) {
+ src = `/ww/${this.nid}`;
+ }
+ const setIframeRef = (elem) => {
+ // TODO ignoring null here is probably a bad idea.
+ if(!_.isNull(elem)) {
+ this.iframe = elem;
+ }
+ };
+
+ return (
+
+ );
+
+ }
+}
diff --git a/scripts/components/Feedback.jsx b/scripts/components/Feedback.jsx
new file mode 100644
index 0000000..c91fc9e
--- /dev/null
+++ b/scripts/components/Feedback.jsx
@@ -0,0 +1,214 @@
+import React from 'react';
+import Recaptcha from 'react-recaptcha';
+import { FormGroup, FormControl, Form, ControlLabel, InputGroup, Col, Button } from 'react-bootstrap';
+import isEmail from 'is-email';
+import _ from 'lodash';
+import axios from 'axios';
+
+export default class Feedback extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ referrer: 'No referrer',
+ subject: '',
+ content: '',
+ name: '',
+ email: '',
+ org: ''
+ };
+ }
+
+ componentWillMount() {
+ if (document.referrer) {
+ this.setState({referrer : document.referrer});
+ }
+ }
+
+ handleChange(e) {
+ let nextState = _.cloneDeep(this.state);
+ nextState[e.target.id] = e.target.value;
+ this.setState(nextState);
+ }
+
+ validateField(fieldName) {
+ if (fieldName === 'subject') {
+ const length = this.state.subject.length;
+ if (length > 10) return 'success';
+ else if (length > 5) return 'warning';
+ else if (length > 0) return 'error';
+ }
+ if (fieldName === 'name') {
+ const length = this.state.name.length;
+ if (length > 4) return 'success';
+ else if (length > 2) return 'warning';
+ else if (length > 0) return 'error';
+ }
+ if (fieldName === 'email') {
+ const length = this.state.email.length;
+ if (isEmail(this.state.email))
+ return 'success';
+ else if (length > 0)
+ return 'error';
+ }
+ if (fieldName === 'org') {
+ const length = this.state.org.length;
+ if (length > 2) return 'success';
+ else if (length > 1) return 'warning';
+ else if (length > 0) return 'error';
+ }
+ }
+
+ submitForm() {
+ let that = this;
+ axios.post('/feedback',this.state)
+ .then(function (response) {
+ that.setState({submittedForm: true, ticket: response.data.ticket});
+ })
+ .catch(function (error) {
+ console.log(error);
+ });
+ }
+
+ verifyRecaptcha(response) {
+ this.setState({recaptcha: response});
+ }
+
+ loadRecaptcha() {
+ console.log('loaded recaptcha');
+ }
+
+ formIsValid() {
+ return (this.validateField('subject') === 'success'
+ && this.validateField('name') === 'success'
+ && this.validateField('email') === 'success'
+ && this.validateField('org') === 'success'
+ && this.state.recaptcha
+ )
+ }
+
+ renderForm() {
+ let submit = this.formIsValid() ? (
+
Your issue has been assigned the ticket number {this.state.ticket}.
{geneDoc.description}
+