initial commit
This commit is contained in:
parent
f2c2193866
commit
a2e37ff2b6
|
@ -1,73 +1,23 @@
|
||||||
# ---> JetBrains
|
# See https://help.github.com/ignore-files/ for more about ignoring files.
|
||||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
|
|
||||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
|
||||||
|
|
||||||
# User-specific stuff
|
# dependencies
|
||||||
.idea/**/workspace.xml
|
/node_modules
|
||||||
.idea/**/tasks.xml
|
|
||||||
.idea/**/usage.statistics.xml
|
|
||||||
.idea/**/dictionaries
|
|
||||||
.idea/**/shelf
|
|
||||||
|
|
||||||
# Generated files
|
# testing
|
||||||
.idea/**/contentModel.xml
|
/coverage
|
||||||
|
|
||||||
# Sensitive or high-churn files
|
# production
|
||||||
.idea/**/dataSources/
|
/build
|
||||||
.idea/**/dataSources.ids
|
|
||||||
.idea/**/dataSources.local.xml
|
|
||||||
.idea/**/sqlDataSources.xml
|
|
||||||
.idea/**/dynamic.xml
|
|
||||||
.idea/**/uiDesigner.xml
|
|
||||||
.idea/**/dbnavigator.xml
|
|
||||||
|
|
||||||
# Gradle
|
# misc
|
||||||
.idea/**/gradle.xml
|
.DS_Store
|
||||||
.idea/**/libraries
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
# Gradle and Maven with auto-import
|
npm-debug.log*
|
||||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
yarn-debug.log*
|
||||||
# since they will be recreated, and may cause churn. Uncomment if using
|
yarn-error.log*
|
||||||
# auto-import.
|
|
||||||
# .idea/artifacts
|
|
||||||
# .idea/compiler.xml
|
|
||||||
# .idea/jarRepositories.xml
|
|
||||||
# .idea/modules.xml
|
|
||||||
# .idea/*.iml
|
|
||||||
# .idea/modules
|
|
||||||
# *.iml
|
|
||||||
# *.ipr
|
|
||||||
|
|
||||||
# CMake
|
|
||||||
cmake-build-*/
|
|
||||||
|
|
||||||
# Mongo Explorer plugin
|
|
||||||
.idea/**/mongoSettings.xml
|
|
||||||
|
|
||||||
# File-based project format
|
|
||||||
*.iws
|
|
||||||
|
|
||||||
# IntelliJ
|
|
||||||
out/
|
|
||||||
|
|
||||||
# mpeltonen/sbt-idea plugin
|
|
||||||
.idea_modules/
|
|
||||||
|
|
||||||
# JIRA plugin
|
|
||||||
atlassian-ide-plugin.xml
|
|
||||||
|
|
||||||
# Cursive Clojure plugin
|
|
||||||
.idea/replstate.xml
|
|
||||||
|
|
||||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
|
||||||
com_crashlytics_export_strings.xml
|
|
||||||
crashlytics.properties
|
|
||||||
crashlytics-build.properties
|
|
||||||
fabric.properties
|
|
||||||
|
|
||||||
# Editor-based Rest Client
|
|
||||||
.idea/httpRequests
|
|
||||||
|
|
||||||
# Android studio 3.1+ serialized cache file
|
|
||||||
.idea/caches/build_file_checksums.ser
|
|
||||||
|
|
||||||
|
.idea/
|
22
README.md
22
README.md
|
@ -1,20 +1,2 @@
|
||||||
#### 从命令行创建一个新的仓库
|
# SnowView
|
||||||
|
add a jenkins test
|
||||||
```bash
|
|
||||||
touch README.md
|
|
||||||
git init
|
|
||||||
git add README.md
|
|
||||||
git commit -m "first commit"
|
|
||||||
git remote add origin https://git.trustie.net/Wusj/snowview.git
|
|
||||||
git push -u origin master
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 从命令行推送已经创建的仓库
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git remote add origin https://git.trustie.net/Wusj/snowview.git
|
|
||||||
git push -u origin master
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,54 @@
|
||||||
|
{
|
||||||
|
"name": "snowview",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/chance": "^0.7.35",
|
||||||
|
"@types/classnames": "^2.2.4",
|
||||||
|
"@types/d3": "^4.11.1",
|
||||||
|
"@types/jquery": "^3.2.16",
|
||||||
|
"@types/lodash": "^4.14.85",
|
||||||
|
"@types/prismjs": "^1.6.5",
|
||||||
|
"@types/react-redux": "^5.0.12",
|
||||||
|
"@types/react-router-dom": "^4.2.2",
|
||||||
|
"@types/react-swipeable-views": "^0.12.1",
|
||||||
|
"chance": "^1.0.12",
|
||||||
|
"classnames": "^2.2.6",
|
||||||
|
"d3": "^4.11.0",
|
||||||
|
"immutable": "^3.8.2",
|
||||||
|
"jquery": "^3.2.1",
|
||||||
|
"js-snackbar": "^0.3.1",
|
||||||
|
"lodash": "^4.17.4",
|
||||||
|
"material-ui": "1.0.0-beta.21",
|
||||||
|
"material-ui-icons": "^1.0.0-beta.17",
|
||||||
|
"material-ui-next-types": "^1.0.0",
|
||||||
|
"prismjs": "^1.8.4",
|
||||||
|
"react": "^16.3.2",
|
||||||
|
"react-dom": "^16.1.1",
|
||||||
|
"react-redux": "^5.0.6",
|
||||||
|
"react-router-dom": "^4.2.2",
|
||||||
|
"react-scripts-ts": "2.8.0",
|
||||||
|
"react-swipeable-views": "^0.12.13",
|
||||||
|
"redux": "^3.7.2",
|
||||||
|
"redux-thunk": "^2.2.0",
|
||||||
|
"ts-option": "^1.1.4",
|
||||||
|
"typeface-roboto": "0.0.45",
|
||||||
|
"typescript-fsa": "^2.5.0",
|
||||||
|
"typescript-fsa-reducers": "^0.4.4",
|
||||||
|
"typescript-fsa-redux-thunk": "^2.0.0-beta.6"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"start": "react-scripts-ts start",
|
||||||
|
"build": "react-scripts-ts build",
|
||||||
|
"test": "react-scripts-ts test --env=jsdom",
|
||||||
|
"eject": "react-scripts-ts eject"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/jest": "^21.1.6",
|
||||||
|
"@types/node": "^8.0.51",
|
||||||
|
"@types/react": "^16.0.22",
|
||||||
|
"@types/react-dom": "^16.0.3",
|
||||||
|
"redux-devtools-extension": "^2.13.2"
|
||||||
|
},
|
||||||
|
"homepage": "/SnowGraph"
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
g.fade {
|
||||||
|
display: none;
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
/*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}figure{margin:1em 40px}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent;-webkit-text-decoration-skip:objects}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}dfn{font-style:italic}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:sans-serif;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item}canvas{display:inline-block}template{display:none}[hidden]{display:none}/*# sourceMappingURL=normalize.min.css.map */
|
|
@ -0,0 +1,227 @@
|
||||||
|
/* http://prismjs.com/download.html?themes=prism-coy&languages=markup+css+clike+javascript+java */
|
||||||
|
/**
|
||||||
|
* prism.js Coy theme for JavaScript, CoffeeScript, CSS and HTML
|
||||||
|
* Based on https://github.com/tshedor/workshop-wp-theme (Example: http://workshop.kansan.com/category/sessions/basics or http://workshop.timshedor.com/category/sessions/basics);
|
||||||
|
* @author Tim Shedor
|
||||||
|
*/
|
||||||
|
|
||||||
|
code[class*="language-"],
|
||||||
|
pre[class*="language-"] {
|
||||||
|
color: black;
|
||||||
|
background: none;
|
||||||
|
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
|
||||||
|
text-align: left;
|
||||||
|
white-space: pre;
|
||||||
|
word-spacing: normal;
|
||||||
|
word-break: normal;
|
||||||
|
word-wrap: normal;
|
||||||
|
line-height: 1.5;
|
||||||
|
|
||||||
|
-moz-tab-size: 4;
|
||||||
|
-o-tab-size: 4;
|
||||||
|
tab-size: 4;
|
||||||
|
|
||||||
|
-webkit-hyphens: none;
|
||||||
|
-moz-hyphens: none;
|
||||||
|
-ms-hyphens: none;
|
||||||
|
hyphens: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Code blocks */
|
||||||
|
pre[class*="language-"] {
|
||||||
|
position: relative;
|
||||||
|
margin: .5em 0;
|
||||||
|
overflow: visible;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
pre[class*="language-"]>code {
|
||||||
|
position: relative;
|
||||||
|
border-left: 10px solid #358ccb;
|
||||||
|
box-shadow: -1px 0px 0px 0px #358ccb, 0px 0px 0px 1px #dfdfdf;
|
||||||
|
background-color: #fdfdfd;
|
||||||
|
background-image: linear-gradient(transparent 50%, rgba(69, 142, 209, 0.04) 50%);
|
||||||
|
background-size: 3em 3em;
|
||||||
|
background-origin: content-box;
|
||||||
|
background-attachment: local;
|
||||||
|
}
|
||||||
|
|
||||||
|
code[class*="language"] {
|
||||||
|
max-height: inherit;
|
||||||
|
height: 100%;
|
||||||
|
padding: 0 1em;
|
||||||
|
display: block;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Margin bottom to accomodate shadow */
|
||||||
|
:not(pre) > code[class*="language-"],
|
||||||
|
pre[class*="language-"] {
|
||||||
|
background-color: #fdfdfd;
|
||||||
|
-webkit-box-sizing: border-box;
|
||||||
|
-moz-box-sizing: border-box;
|
||||||
|
box-sizing: border-box;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Inline code */
|
||||||
|
:not(pre) > code[class*="language-"] {
|
||||||
|
position: relative;
|
||||||
|
padding: .2em;
|
||||||
|
border-radius: 0.3em;
|
||||||
|
color: #c92c2c;
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||||
|
display: inline;
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre[class*="language-"]:before,
|
||||||
|
pre[class*="language-"]:after {
|
||||||
|
content: '';
|
||||||
|
z-index: -2;
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0.75em;
|
||||||
|
left: 0.18em;
|
||||||
|
width: 40%;
|
||||||
|
height: 20%;
|
||||||
|
max-height: 13em;
|
||||||
|
box-shadow: 0px 13px 8px #979797;
|
||||||
|
-webkit-transform: rotate(-2deg);
|
||||||
|
-moz-transform: rotate(-2deg);
|
||||||
|
-ms-transform: rotate(-2deg);
|
||||||
|
-o-transform: rotate(-2deg);
|
||||||
|
transform: rotate(-2deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
:not(pre) > code[class*="language-"]:after,
|
||||||
|
pre[class*="language-"]:after {
|
||||||
|
right: 0.75em;
|
||||||
|
left: auto;
|
||||||
|
-webkit-transform: rotate(2deg);
|
||||||
|
-moz-transform: rotate(2deg);
|
||||||
|
-ms-transform: rotate(2deg);
|
||||||
|
-o-transform: rotate(2deg);
|
||||||
|
transform: rotate(2deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.comment,
|
||||||
|
.token.block-comment,
|
||||||
|
.token.prolog,
|
||||||
|
.token.doctype,
|
||||||
|
.token.cdata {
|
||||||
|
color: #7D8B99;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.punctuation {
|
||||||
|
color: #5F6364;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.property,
|
||||||
|
.token.tag,
|
||||||
|
.token.boolean,
|
||||||
|
.token.number,
|
||||||
|
.token.function-name,
|
||||||
|
.token.constant,
|
||||||
|
.token.symbol,
|
||||||
|
.token.deleted {
|
||||||
|
color: #c92c2c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.selector,
|
||||||
|
.token.attr-name,
|
||||||
|
.token.string,
|
||||||
|
.token.char,
|
||||||
|
.token.function,
|
||||||
|
.token.builtin,
|
||||||
|
.token.inserted {
|
||||||
|
color: #2f9c0a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.operator,
|
||||||
|
.token.entity,
|
||||||
|
.token.url,
|
||||||
|
.token.variable {
|
||||||
|
color: #a67f59;
|
||||||
|
background: rgba(255, 255, 255, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.atrule,
|
||||||
|
.token.attr-value,
|
||||||
|
.token.keyword,
|
||||||
|
.token.class-name {
|
||||||
|
color: #1990b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.regex,
|
||||||
|
.token.important {
|
||||||
|
color: #e90;
|
||||||
|
}
|
||||||
|
|
||||||
|
.language-css .token.string,
|
||||||
|
.style .token.string {
|
||||||
|
color: #a67f59;
|
||||||
|
background: rgba(255, 255, 255, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.important {
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.bold {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.token.italic {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.token.entity {
|
||||||
|
cursor: help;
|
||||||
|
}
|
||||||
|
|
||||||
|
.namespace {
|
||||||
|
opacity: .7;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 767px) {
|
||||||
|
pre[class*="language-"]:before,
|
||||||
|
pre[class*="language-"]:after {
|
||||||
|
bottom: 14px;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Plugin styles */
|
||||||
|
.token.tab:not(:empty):before,
|
||||||
|
.token.cr:before,
|
||||||
|
.token.lf:before {
|
||||||
|
color: #e0d7d1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Plugin styles: Line Numbers */
|
||||||
|
pre[class*="language-"].line-numbers {
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre[class*="language-"].line-numbers code {
|
||||||
|
padding-left: 3.8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre[class*="language-"].line-numbers .line-numbers-rows {
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Plugin styles: Line Highlight */
|
||||||
|
pre[class*="language-"][data-line] {
|
||||||
|
padding-top: 0;
|
||||||
|
padding-bottom: 0;
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
pre[data-line] code {
|
||||||
|
position: relative;
|
||||||
|
padding-left: 4em;
|
||||||
|
}
|
||||||
|
pre .line-highlight {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 3.8 KiB |
|
@ -0,0 +1,19 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
|
<meta name="theme-color" content="#000000">
|
||||||
|
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
|
||||||
|
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
|
||||||
|
<link rel="stylesheet" href="%PUBLIC_URL%/css/normalize.css">
|
||||||
|
<link rel="stylesheet" href="%PUBLIC_URL%/css/prism.css">
|
||||||
|
<link rel="stylesheet" href="%PUBLIC_URL%/css/main.css">
|
||||||
|
<link href="https://fonts.googleapis.com/css?family=Lato" rel="stylesheet">
|
||||||
|
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500" rel="stylesheet">
|
||||||
|
<title>Software Knowledge Graph</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"short_name": "React App",
|
||||||
|
"name": "Create React App Sample",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "favicon.ico",
|
||||||
|
"sizes": "192x192",
|
||||||
|
"type": "image/png"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"start_url": "./index.html",
|
||||||
|
"display": "standalone",
|
||||||
|
"theme_color": "#000000",
|
||||||
|
"background_color": "#ffffff"
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import { Route, Switch } from 'react-router';
|
||||||
|
import Sidebar from './components/Sidebar/Sidebar';
|
||||||
|
import appRoutes from './routes/app';
|
||||||
|
import { withStyles } from 'material-ui';
|
||||||
|
import appStyle, { AppStyle } from './variables/styles/AppStyle';
|
||||||
|
import Footer from './components/Footer/Footer';
|
||||||
|
|
||||||
|
const image = require('./assets/img/sidebar.jpg');
|
||||||
|
const logo = require('./assets/img/logo.png');
|
||||||
|
|
||||||
|
class App extends React.Component<AppStyle, {}> {
|
||||||
|
render() {
|
||||||
|
const {classes} = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Sidebar logo={logo} logoText="" image={image} routes={appRoutes}/>
|
||||||
|
<div className={classes.mainPanel}>
|
||||||
|
<div className={classes.content}>
|
||||||
|
<Switch>
|
||||||
|
{appRoutes.map((prop, key) =>
|
||||||
|
<Route exact={prop.exact} path={prop.path} component={prop.component} key={key}/>
|
||||||
|
)}
|
||||||
|
</Switch>
|
||||||
|
</div>
|
||||||
|
<Footer/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withStyles(appStyle)<{}>(App);
|
|
@ -0,0 +1,9 @@
|
||||||
|
declare module '*.jpg' {
|
||||||
|
const content: string;
|
||||||
|
export default content;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module '*.png' {
|
||||||
|
const content: string;
|
||||||
|
export default content;
|
||||||
|
}
|
|
@ -0,0 +1,61 @@
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: #EEEEEE;
|
||||||
|
color: #3C4858;
|
||||||
|
margin: 0;
|
||||||
|
font-family: Roboto, Helvetica, Arial, sans-serif;
|
||||||
|
font-weight: 300;
|
||||||
|
line-height: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 3em;
|
||||||
|
line-height: 1.15em;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 2.4em;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: 1.825em;
|
||||||
|
line-height: 1.4em;
|
||||||
|
margin: 20px 0 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
font-size: 1.3em;
|
||||||
|
line-height: 1.4em;
|
||||||
|
}
|
||||||
|
|
||||||
|
h5 {
|
||||||
|
font-size: 1.25em;
|
||||||
|
line-height: 1.4em;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h6 {
|
||||||
|
font-size: 1em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
body, h1, h2, h3, h4, h5, h6 {
|
||||||
|
font-family: "Roboto", "Helvetica", "Arial", sans-serif;
|
||||||
|
font-weight: 300;
|
||||||
|
line-height: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #9c27b0;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover, a:focus {
|
||||||
|
color: #89229b;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
margin: 0;
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 251 KiB |
Binary file not shown.
After Width: | Height: | Size: 19 KiB |
Binary file not shown.
After Width: | Height: | Size: 96 KiB |
|
@ -0,0 +1,51 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import * as classNames from 'classnames';
|
||||||
|
import withStyles from 'material-ui/styles/withStyles';
|
||||||
|
import { Card, CardHeader, CardContent, CardActions } from 'material-ui';
|
||||||
|
import { RegularCardStyle, default as regularCardStyle } from '../../variables/styles/RegularCardStyle';
|
||||||
|
|
||||||
|
interface RegularCardProps {
|
||||||
|
plainCard?: boolean;
|
||||||
|
headerColor?: 'orange' | 'green' | 'red' | 'blue' | 'purple';
|
||||||
|
cardTitle?: {};
|
||||||
|
cardSubtitle?: {};
|
||||||
|
footer?: {};
|
||||||
|
}
|
||||||
|
|
||||||
|
class RegularCard extends React.Component<RegularCardProps & RegularCardStyle, {}> {
|
||||||
|
public static defaultProps: Partial<RegularCardProps> = {
|
||||||
|
plainCard: false,
|
||||||
|
headerColor: 'purple'
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {classes, plainCard, headerColor, cardTitle, cardSubtitle, children, footer} = this.props;
|
||||||
|
|
||||||
|
const plainCardClasses = classNames({
|
||||||
|
[' ' + classes.cardPlain]: plainCard
|
||||||
|
});
|
||||||
|
const cardPlainHeaderClasses = classNames({
|
||||||
|
[' ' + classes.cardPlainHeader]: plainCard
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className={classes.card + plainCardClasses}>
|
||||||
|
{cardTitle && <CardHeader
|
||||||
|
classes={{
|
||||||
|
root: classes.cardHeader + ' ' + classes[headerColor + 'CardHeader'] + cardPlainHeaderClasses,
|
||||||
|
title: classes.cardTitle,
|
||||||
|
subheader: classes.cardSubtitle
|
||||||
|
}}
|
||||||
|
title={cardTitle}
|
||||||
|
subheader={cardSubtitle}
|
||||||
|
/>}
|
||||||
|
<CardContent>{children}</CardContent>
|
||||||
|
{footer !== undefined ? (
|
||||||
|
<CardActions className={classes.cardActions}>{footer}</CardActions>
|
||||||
|
) : null}
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withStyles(regularCardStyle)<RegularCardProps>(RegularCard);
|
|
@ -0,0 +1,56 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import SwipeableViews from 'react-swipeable-views';
|
||||||
|
|
||||||
|
interface CarouselProp {
|
||||||
|
slideCount: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CarouselState {
|
||||||
|
index: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Carousel extends React.Component<CarouselProp, CarouselState> {
|
||||||
|
timer?: number;
|
||||||
|
|
||||||
|
state: CarouselState = {
|
||||||
|
index: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
startInterval = () => {
|
||||||
|
if (this.timer) {
|
||||||
|
window.clearInterval(this.timer);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.timer = window.setInterval(this.handleInterval, 6000);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleInterval = () => {
|
||||||
|
const { slideCount } = this.props;
|
||||||
|
|
||||||
|
this.setState(prevState => ({
|
||||||
|
index: (prevState.index + 1) % slideCount
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.startInterval();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
if (this.timer) {
|
||||||
|
window.clearInterval(this.timer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {children} = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SwipeableViews index={this.state.index}>
|
||||||
|
{children}
|
||||||
|
</SwipeableViews>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Carousel;
|
|
@ -0,0 +1,57 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import { Dialog, withStyles, WithStyles, Typography } from 'material-ui';
|
||||||
|
import * as Prism from 'prismjs';
|
||||||
|
import { Theme } from 'material-ui/styles';
|
||||||
|
|
||||||
|
const styles = (theme: Theme) => ({
|
||||||
|
container: {
|
||||||
|
margin: theme.spacing.unit * 2,
|
||||||
|
overflow: 'auto',
|
||||||
|
wordBreak: 'break-all',
|
||||||
|
whiteSpace: 'pre-wrap',
|
||||||
|
},
|
||||||
|
button: {
|
||||||
|
display: 'inline',
|
||||||
|
}
|
||||||
|
}) as React.CSSProperties;
|
||||||
|
|
||||||
|
interface CodeModalProps {
|
||||||
|
content: string;
|
||||||
|
label: string;
|
||||||
|
code: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
type CodeModalStyle = WithStyles<'container' | 'button'>;
|
||||||
|
|
||||||
|
class CodeModal extends React.Component<CodeModalProps & CodeModalStyle, { open: boolean }> {
|
||||||
|
state = {
|
||||||
|
open: false
|
||||||
|
};
|
||||||
|
|
||||||
|
handleClickOpen = () => {
|
||||||
|
this.setState({open: true});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleRequestClose = () => {
|
||||||
|
this.setState({open: false});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {classes, code, content, label} = this.props;
|
||||||
|
|
||||||
|
const c = code ? Prism.highlight(content, Prism.languages.javascript) : content;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span>
|
||||||
|
<Typography component="a" {...{href: '#', onClick: this.handleClickOpen}}>
|
||||||
|
{label}
|
||||||
|
</Typography>
|
||||||
|
<Dialog fullWidth={true} maxWidth="md" onRequestClose={this.handleRequestClose} open={this.state.open}>
|
||||||
|
<pre className={classes.container} dangerouslySetInnerHTML={{__html: c}}/>
|
||||||
|
</Dialog>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withStyles(styles)<CodeModalProps>(CodeModal);
|
|
@ -0,0 +1,148 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import * as d3 from 'd3';
|
||||||
|
import { ChordGroup, ChordSubgroup, Chord, color, rgb } from 'd3';
|
||||||
|
import { Chords } from 'd3-chord';
|
||||||
|
|
||||||
|
interface D3GraphProps {
|
||||||
|
id: string;
|
||||||
|
data: number[][];
|
||||||
|
colors: string[];
|
||||||
|
labels: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
function mix(color1: string, color2: string) {
|
||||||
|
const c1 = color(color1);
|
||||||
|
const c2 = color(color2);
|
||||||
|
const newColor = rgb(
|
||||||
|
(c1.rgb().r + c2.rgb().r) / 2,
|
||||||
|
(c1.rgb().g + c2.rgb().g) / 2,
|
||||||
|
(c1.rgb().b + c2.rgb().b) / 2
|
||||||
|
);
|
||||||
|
return newColor.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
class D3Chord extends React.Component<D3GraphProps, {}> {
|
||||||
|
|
||||||
|
canvas: d3.Selection<SVGGElement, Chords, HTMLElement, {}>;
|
||||||
|
|
||||||
|
ribbons: d3.Selection<SVGGElement, Chord, SVGGElement, {}>;
|
||||||
|
|
||||||
|
update = () => {
|
||||||
|
const outerRadius = 30, innerRadius = outerRadius - 2;
|
||||||
|
|
||||||
|
const {data, colors, labels} = this.props;
|
||||||
|
|
||||||
|
const chord = d3.chord()
|
||||||
|
.padAngle(0.05)
|
||||||
|
.sortSubgroups(d3.descending);
|
||||||
|
|
||||||
|
const arc = d3.arc<ChordGroup>()
|
||||||
|
.innerRadius(innerRadius)
|
||||||
|
.outerRadius(outerRadius);
|
||||||
|
|
||||||
|
const ribbon = d3.ribbon<Chord, ChordSubgroup>()
|
||||||
|
.radius(innerRadius);
|
||||||
|
|
||||||
|
const svg = d3.select<SVGSVGElement, {}>(`#${this.props.id}`);
|
||||||
|
|
||||||
|
const width = 100;
|
||||||
|
const height = 100;
|
||||||
|
|
||||||
|
const binaryData: number[][] = [];
|
||||||
|
for (let i = 0; i < data.length; i++) {
|
||||||
|
binaryData.push([]);
|
||||||
|
for (let j = 0; j < data[i].length; j++) {
|
||||||
|
binaryData[i].push(Math.log(data[i][j] + 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.canvas = svg
|
||||||
|
.append<SVGGElement>('g')
|
||||||
|
.attr('transform', `translate(${width / 2}, ${height / 2})`)
|
||||||
|
.datum(chord(binaryData))
|
||||||
|
.on('onMouseLeave', this.onMouseLeave);
|
||||||
|
|
||||||
|
this.ribbons = this.canvas.append<SVGGElement>('g')
|
||||||
|
.attr('class', 'ribbons')
|
||||||
|
.selectAll('path')
|
||||||
|
.data(chords => chords)
|
||||||
|
.enter()
|
||||||
|
.append<SVGGElement>('g');
|
||||||
|
|
||||||
|
const group = this.canvas.append<SVGGElement>('g')
|
||||||
|
.attr('class', 'groups')
|
||||||
|
.selectAll('g')
|
||||||
|
.data<ChordGroup>(chords => chords.groups)
|
||||||
|
.enter().append<SVGGElement>('g')
|
||||||
|
.on('onMouseOver', this.onMouseOver);
|
||||||
|
|
||||||
|
group.append('path')
|
||||||
|
.attr('id', d => `p${d.index}`)
|
||||||
|
.style('fill', d => colors[d.index])
|
||||||
|
.attr('d', arc);
|
||||||
|
|
||||||
|
group.append('title').html(d => labels[d.index]);
|
||||||
|
|
||||||
|
this.ribbons
|
||||||
|
.append('path')
|
||||||
|
.attr('d', ribbon)
|
||||||
|
.style('fill', d => mix(colors[d.source.index], colors[d.target.index]))
|
||||||
|
.style('stroke-width', '0.1')
|
||||||
|
.style('stroke', 'black');
|
||||||
|
|
||||||
|
this.ribbons
|
||||||
|
.append('title')
|
||||||
|
.html(d => data[d.source.index][d.target.index].toString());
|
||||||
|
|
||||||
|
group.append('svg:text')
|
||||||
|
.style('font-size', '4px')
|
||||||
|
.style('text-anchor', 'middle')
|
||||||
|
.attr('dx', function (d: ChordGroup) {
|
||||||
|
let anchor = (d.startAngle + d.endAngle) / 2;
|
||||||
|
let radius = outerRadius + 10;
|
||||||
|
return radius * Math.sin(anchor);
|
||||||
|
})
|
||||||
|
.attr('dy', function (d: ChordGroup) {
|
||||||
|
let anchor = (d.startAngle + d.endAngle) / 2;
|
||||||
|
let radius = outerRadius + 10;
|
||||||
|
return -radius * Math.cos(anchor);
|
||||||
|
})
|
||||||
|
.text(function (d: ChordGroup) {
|
||||||
|
return labels[d.index].substr(0, labels[d.index].indexOf('('));
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
onMouseOver = (d: {}, i: {}) => {
|
||||||
|
this.ribbons.classed('fade', p => {
|
||||||
|
return p.source.index !== i && p.target.index !== i;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onMouseLeave = () => {
|
||||||
|
this.ribbons.classed('fade', false);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate() {
|
||||||
|
this.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div style={{width: '100%'}}>
|
||||||
|
<svg
|
||||||
|
width="100%"
|
||||||
|
height={600}
|
||||||
|
viewBox="0,0,100,100"
|
||||||
|
id={this.props.id}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default D3Chord;
|
|
@ -0,0 +1,23 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import { withStyles } from 'material-ui';
|
||||||
|
|
||||||
|
import footerStyle, { FooterStyle } from '../../variables/styles/FooterStyle';
|
||||||
|
|
||||||
|
class Footer extends React.Component<FooterStyle, {}> {
|
||||||
|
render() {
|
||||||
|
const {classes} = this.props;
|
||||||
|
return (
|
||||||
|
<footer className={classes.footer}>
|
||||||
|
<div className={classes.container}>
|
||||||
|
<p className={classes.right}>
|
||||||
|
<span>
|
||||||
|
Copyright 2018, Software Engineering Institute, Peking University. All right reserved.
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withStyles(footerStyle)<{}>(Footer);
|
|
@ -0,0 +1,53 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import withStyles from 'material-ui/styles/withStyles';
|
||||||
|
import { Table, TableHead, TableRow, TableCell, TableBody } from 'material-ui';
|
||||||
|
import matTableStyle, { MatTableStyle } from '../../variables/styles/MatTableStyle';
|
||||||
|
|
||||||
|
type CellElement = string | JSX.Element;
|
||||||
|
type Row = {
|
||||||
|
key?: string;
|
||||||
|
columns: CellElement[];
|
||||||
|
};
|
||||||
|
|
||||||
|
interface MatTableProps {
|
||||||
|
tableHead?: string[];
|
||||||
|
tableData: Row[];
|
||||||
|
}
|
||||||
|
|
||||||
|
class MatTable extends React.Component<MatTableProps & MatTableStyle, {}> {
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {classes, tableHead, tableData} = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classes.tableResponsive}>
|
||||||
|
<Table className={classes.table}>
|
||||||
|
{tableHead !== undefined ? (
|
||||||
|
<TableHead className={classes.tableHeader}>
|
||||||
|
<TableRow>
|
||||||
|
{tableHead.map((prop, key) => (
|
||||||
|
<TableCell
|
||||||
|
className={classes.tableCell + ' ' + classes.tableHeadCell}
|
||||||
|
key={key}
|
||||||
|
>
|
||||||
|
{prop}
|
||||||
|
</TableCell>
|
||||||
|
))}
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
) : null}
|
||||||
|
<TableBody>
|
||||||
|
{tableData.map((row, k1) =>
|
||||||
|
<TableRow className={classes.tableRow} key={row.key || k1}>
|
||||||
|
{row.columns.map((prop, k2) => (
|
||||||
|
<TableCell className={classes.tableCell} key={k2}> {prop} </TableCell>
|
||||||
|
))}
|
||||||
|
</TableRow>)}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withStyles(matTableStyle)<MatTableProps>(MatTable);
|
|
@ -0,0 +1,69 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import { withStyles, WithStyles } from 'material-ui';
|
||||||
|
import ExpandMoreIcon from 'material-ui-icons/ExpandMore';
|
||||||
|
import ExpandLessIcon from 'material-ui-icons/ExpandLess';
|
||||||
|
import { Theme } from 'material-ui/styles';
|
||||||
|
import Typography from 'material-ui/Typography';
|
||||||
|
|
||||||
|
const styles = (theme: Theme) => ({
|
||||||
|
detail: {
|
||||||
|
borderWidth: '0.25px',
|
||||||
|
borderStyle: 'solid',
|
||||||
|
borderColor: '#7F7F7F',
|
||||||
|
padding: '8px',
|
||||||
|
margin: '8px',
|
||||||
|
},
|
||||||
|
cellRank: {
|
||||||
|
width: '12%'
|
||||||
|
},
|
||||||
|
cellMain: {
|
||||||
|
borderBottom: 'none',
|
||||||
|
overflowWrap: 'normal',
|
||||||
|
whiteSpace: 'normal'
|
||||||
|
},
|
||||||
|
cellTitle: {
|
||||||
|
fontSize: 16,
|
||||||
|
color: '#3f51b5',
|
||||||
|
fontWeight: 'bold' as 'bold',
|
||||||
|
display: 'inline',
|
||||||
|
},
|
||||||
|
highlight: {
|
||||||
|
background: theme.palette.primary[50]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
interface RankRowProps {
|
||||||
|
initExpand: boolean;
|
||||||
|
title: string;
|
||||||
|
detail: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type RankRowStyle = WithStyles<'detail' | 'cellRank' | 'cellMain' | 'cellTitle' | 'highlight'>;
|
||||||
|
|
||||||
|
class RankRow extends React.Component<RankRowProps & RankRowStyle, { expand: boolean }> {
|
||||||
|
state = {
|
||||||
|
expand: this.props.initExpand
|
||||||
|
};
|
||||||
|
|
||||||
|
handleExpandMore = () => {
|
||||||
|
this.setState({expand: true});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleExpandLess = () => {
|
||||||
|
this.setState({expand: false});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {classes, title, detail} = this.props;
|
||||||
|
return (
|
||||||
|
<div className={classes.cellMain}>
|
||||||
|
<Typography className={classes.cellTitle}>{`${title}`}</Typography>
|
||||||
|
{!this.state.expand && <ExpandMoreIcon onClick={this.handleExpandMore}/>}
|
||||||
|
{this.state.expand && <ExpandLessIcon onClick={this.handleExpandLess}/>}
|
||||||
|
{this.state.expand && <div className={classes.detail} dangerouslySetInnerHTML={{__html: detail}}/>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withStyles(styles)<RankRowProps>(RankRow);
|
|
@ -0,0 +1,80 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { Input, withStyles, WithStyles } from 'material-ui';
|
||||||
|
import { RootState } from '../redux/reducer';
|
||||||
|
import { Dispatch } from 'redux';
|
||||||
|
import { Theme } from 'material-ui/styles';
|
||||||
|
import { ChangeEvent, FormEvent } from 'react';
|
||||||
|
|
||||||
|
const styles = (theme: Theme) => ({
|
||||||
|
container: {
|
||||||
|
margin: theme.spacing.unit * 2
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
width: '100%',
|
||||||
|
},
|
||||||
|
search: {
|
||||||
|
marginLeft: theme.spacing.unit * 2,
|
||||||
|
marginRight: theme.spacing.unit * 2,
|
||||||
|
width: `calc(100% - ${theme.spacing.unit * 4}px)`,
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
interface SearchFormProps {
|
||||||
|
query?: string;
|
||||||
|
predefinedQueries: string[];
|
||||||
|
callback: Function;
|
||||||
|
dispatch: Dispatch<RootState>;
|
||||||
|
}
|
||||||
|
|
||||||
|
type SearchFormStyles = WithStyles<'container' | 'form' | 'search'>;
|
||||||
|
|
||||||
|
class SearchForm extends React.Component<SearchFormProps & SearchFormStyles, { input: string }> {
|
||||||
|
state = {
|
||||||
|
input: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.setState({input: this.props.query || ''});
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps(nextProps: SearchFormProps & SearchFormStyles) {
|
||||||
|
this.setState({input: nextProps.query || ''});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSubmit = (event: FormEvent<HTMLFormElement>) => {
|
||||||
|
event.preventDefault();
|
||||||
|
const {dispatch, callback} = this.props;
|
||||||
|
|
||||||
|
dispatch(callback({query: this.state.input}));
|
||||||
|
}
|
||||||
|
|
||||||
|
handleChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
this.setState({input: event.target.value});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {classes, predefinedQueries} = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form className={classes.form} onSubmit={this.handleSubmit}>
|
||||||
|
<Input
|
||||||
|
className={classes.search}
|
||||||
|
type="search"
|
||||||
|
placeholder="Ask a question here..."
|
||||||
|
value={this.state.input}
|
||||||
|
onChange={this.handleChange}
|
||||||
|
inputProps={{list: 'predefined-queries'}}
|
||||||
|
/>
|
||||||
|
<datalist id="predefined-queries">
|
||||||
|
{predefinedQueries.map(q => <option key={q} value={q}/>)}
|
||||||
|
</datalist>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withStyles(styles)<{
|
||||||
|
predefinedQueries: string[], callback: Function, query?: string
|
||||||
|
}>(connect()(SearchForm));
|
|
@ -0,0 +1,84 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import { Link, NavLink } from 'react-router-dom';
|
||||||
|
import * as classNames from 'classnames';
|
||||||
|
import withStyles from 'material-ui/styles/withStyles';
|
||||||
|
import { Drawer } from 'material-ui';
|
||||||
|
import { default as List, ListItem, ListItemText, ListItemIcon } from 'material-ui/List';
|
||||||
|
import { AppRoute } from '../../routes/app';
|
||||||
|
import sidebarStyle, { SidebarStyle } from '../../variables/styles/SidebarStyle';
|
||||||
|
|
||||||
|
interface SidebarProps {
|
||||||
|
logo: string;
|
||||||
|
logoText: string;
|
||||||
|
image: string;
|
||||||
|
routes: AppRoute[];
|
||||||
|
}
|
||||||
|
|
||||||
|
function activeRoute(routeName: string) {
|
||||||
|
const index = window.location.pathname.indexOf(routeName);
|
||||||
|
if (index === -1) { return false; }
|
||||||
|
if (index + routeName.length === window.location.pathname.length) { return true; }
|
||||||
|
return window.location.pathname.charAt(index + routeName.length) === '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
class Sidebar extends React.Component<SidebarProps & SidebarStyle, {}> {
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {classes, logo, logoText, image, routes} = this.props;
|
||||||
|
const links = (
|
||||||
|
<List className={classes.list}>
|
||||||
|
{routes.map((route) => {
|
||||||
|
const listItemClasses = classNames({
|
||||||
|
[' ' + classes.blue]: activeRoute(route.path)
|
||||||
|
});
|
||||||
|
const whiteFontClasses = classNames({
|
||||||
|
[' ' + classes.whiteFont]: activeRoute(route.path)
|
||||||
|
});
|
||||||
|
return (
|
||||||
|
<NavLink
|
||||||
|
to={route.path}
|
||||||
|
className={classes.item}
|
||||||
|
activeClassName="active"
|
||||||
|
key={route.sidebarName}
|
||||||
|
>
|
||||||
|
<ListItem button={true} className={classes.itemLink + listItemClasses}>
|
||||||
|
<ListItemIcon className={classes.itemIcon + whiteFontClasses}>
|
||||||
|
<route.icon />
|
||||||
|
</ListItemIcon>
|
||||||
|
<ListItemText
|
||||||
|
primary={route.sidebarName}
|
||||||
|
className={classes.itemText + whiteFontClasses}
|
||||||
|
disableTypography={true}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
</NavLink>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</List>
|
||||||
|
);
|
||||||
|
const brand = (
|
||||||
|
<div className={classes.logo}>
|
||||||
|
<Link to="/" className={classes.logoLink}>
|
||||||
|
<div className={classes.logoImage}>
|
||||||
|
<img src={logo} alt="logo" className={classes.img}/>
|
||||||
|
</div>
|
||||||
|
{logoText}
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Drawer type="permanent" open={true} classes={{paper: classes.drawerPaper}}>
|
||||||
|
{brand}
|
||||||
|
<div className={classes.sidebarWrapper}>{links}</div>
|
||||||
|
<div
|
||||||
|
className={classes.background}
|
||||||
|
style={{backgroundImage: `url(${image})`}}
|
||||||
|
/>
|
||||||
|
</Drawer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withStyles(sidebarStyle)<SidebarProps>(Sidebar);
|
|
@ -0,0 +1,55 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import { Tooltip, withStyles, WithStyles } from 'material-ui';
|
||||||
|
import Typography from 'material-ui/Typography';
|
||||||
|
|
||||||
|
const styles = () => ({
|
||||||
|
statistic: {
|
||||||
|
display: 'inline-flex',
|
||||||
|
flexDirection: 'column' as 'column',
|
||||||
|
width: '100%',
|
||||||
|
marginTop: '20px',
|
||||||
|
marginBottom: '20px',
|
||||||
|
},
|
||||||
|
num: {
|
||||||
|
fontSize: '1.2rem',
|
||||||
|
fontFamily: 'Lato',
|
||||||
|
fontWeight: 400 as 400,
|
||||||
|
textAlign: 'center',
|
||||||
|
lineHeight: '1em',
|
||||||
|
color: '#1B1C1D'
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
fontSize: '0.8em',
|
||||||
|
fontFamily: 'Lato',
|
||||||
|
fontWeight: 700 as 700,
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
overflow: 'hidden' as 'hidden',
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
textAlign: 'center',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
color: 'rgba(0, 0, 0, 0.87)'
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
interface StatisticProps {
|
||||||
|
num: number;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type RankRowStyle = WithStyles<'statistic' | 'num' | 'label'>;
|
||||||
|
|
||||||
|
class Statistic extends React.Component<StatisticProps & RankRowStyle, {}> {
|
||||||
|
render() {
|
||||||
|
const {num, label, classes} = this.props;
|
||||||
|
return (
|
||||||
|
<div className={classes.statistic}>
|
||||||
|
<Typography className={classes.num}>{num}</Typography>
|
||||||
|
<Tooltip title={label} placement="bottom">
|
||||||
|
<Typography className={classes.label}>{label}</Typography>
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withStyles(styles)<StatisticProps>(Statistic);
|
|
@ -0,0 +1,18 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import withStyles from 'material-ui/styles/withStyles';
|
||||||
|
import typographyStyle, { TypographyStyle } from '../../variables/styles/TypographyStyle';
|
||||||
|
|
||||||
|
class P extends React.Component<TypographyStyle, {}> {
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {classes, children} = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<p className={classes.defaultFontStyle + ' ' + classes.pStyle}>
|
||||||
|
{children}
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withStyles(typographyStyle)<{}>(P);
|
|
@ -0,0 +1,278 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import * as d3 from 'd3';
|
||||||
|
import { ForceLink } from 'd3-force';
|
||||||
|
import { Option } from 'ts-option';
|
||||||
|
import D3ForceNode from './D3ForceNode';
|
||||||
|
import D3ForceLink from './D3ForceLink';
|
||||||
|
import { INode } from '../../model';
|
||||||
|
|
||||||
|
const nodeRadius = 50;
|
||||||
|
const arrowSize = 12;
|
||||||
|
|
||||||
|
interface D3ForceProps<N extends INode, R> {
|
||||||
|
id: string;
|
||||||
|
highlight: Option<number>;
|
||||||
|
nodes: N[];
|
||||||
|
links: R[];
|
||||||
|
getNodeColor: (node: N) => string;
|
||||||
|
getNodeLabel: (node: N) => string;
|
||||||
|
getNodeText: (node: N) => string;
|
||||||
|
getLinkID: (link: R) => string;
|
||||||
|
getLinkText: (link: R) => string;
|
||||||
|
getSourceNodeID: (link: R) => string;
|
||||||
|
getTargetNodeID: (link: R) => string;
|
||||||
|
onNodeClick?: (id: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface D3ForceState<N, R> {
|
||||||
|
nodes: D3Node<N>[];
|
||||||
|
links: D3Relation<N, R>[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface D3Node<N> {
|
||||||
|
raw: N;
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
vx?: number;
|
||||||
|
vy?: number;
|
||||||
|
fx?: number | null;
|
||||||
|
fy?: number | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface D3Relation<N, R> {
|
||||||
|
raw: R;
|
||||||
|
type: 'single' | 'repeated';
|
||||||
|
source: D3Node<N>;
|
||||||
|
target: D3Node<N>;
|
||||||
|
x1: number;
|
||||||
|
y1: number;
|
||||||
|
x2: number;
|
||||||
|
y2: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const D3RelationType = {
|
||||||
|
SINGLE: 'single' as 'single',
|
||||||
|
REPEATED: 'repeated' as 'repeated'
|
||||||
|
};
|
||||||
|
|
||||||
|
class D3Force<N extends INode, R> extends React.Component<D3ForceProps<N, R>, D3ForceState<N, R>> {
|
||||||
|
state = {
|
||||||
|
nodes: [] as D3Node<N>[],
|
||||||
|
links: [] as D3Relation<N, R>[]
|
||||||
|
};
|
||||||
|
|
||||||
|
nodes: D3Node<N>[] = [];
|
||||||
|
|
||||||
|
links: D3Relation<N, R>[] = [];
|
||||||
|
|
||||||
|
canvas: SVGGElement | null;
|
||||||
|
|
||||||
|
svg: d3.Selection<SVGGElement, {}, HTMLElement, {}>;
|
||||||
|
|
||||||
|
simulation: d3.Simulation<D3Node<N>, D3Relation<N, R>> = d3.forceSimulation<D3Node<N>>()
|
||||||
|
.force('charge', d3.forceManyBody())
|
||||||
|
.force('collide', d3.forceCollide(nodeRadius * 1.5));
|
||||||
|
|
||||||
|
updateRelation = (rel: D3Relation<N, R>) => {
|
||||||
|
const x1 = rel.source.x;
|
||||||
|
const y1 = rel.source.y;
|
||||||
|
const x2 = rel.target.x;
|
||||||
|
const y2 = rel.target.y;
|
||||||
|
|
||||||
|
return Object.assign({}, rel, {x1, y1, x2, y2});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateLinks = (nextProps: D3ForceProps<N, R>) => {
|
||||||
|
const {links, getLinkID, getSourceNodeID, getTargetNodeID} = nextProps;
|
||||||
|
|
||||||
|
const newLinks = links.filter(l => !this.links.some(lk => getLinkID(lk.raw) === getLinkID(l)));
|
||||||
|
|
||||||
|
this.links = [
|
||||||
|
...this.links.filter(lk => links.some(l => getLinkID(lk.raw) === getLinkID(l))),
|
||||||
|
...newLinks.map(l => ({
|
||||||
|
raw: l,
|
||||||
|
source: this.nodes.find(n => n.raw.getID() === getSourceNodeID(l))!,
|
||||||
|
target: this.nodes.find(n => n.raw.getID() === getTargetNodeID(l))!,
|
||||||
|
x1: 0,
|
||||||
|
y1: 0,
|
||||||
|
x2: 0,
|
||||||
|
y2: 0,
|
||||||
|
type: D3RelationType.SINGLE
|
||||||
|
})
|
||||||
|
)
|
||||||
|
];
|
||||||
|
|
||||||
|
this.links
|
||||||
|
.filter(l => this.links.some(o => l.source === o.target && l.target === o.source))
|
||||||
|
.forEach(l => l.type = D3RelationType.REPEATED);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
updateNodes = (nextProps: D3ForceProps<N, R>) => {
|
||||||
|
const {nodes} = nextProps;
|
||||||
|
|
||||||
|
const newNodes = nodes.filter(n => !this.nodes.some(nd => nd.raw.getID() === n.getID()));
|
||||||
|
|
||||||
|
this.nodes = [
|
||||||
|
...this.nodes.filter((nd: D3Node<N>) => nodes.some(n => nd.raw.getID() === n.getID())),
|
||||||
|
...newNodes.map(n => ({raw: n, x: 0, y: 0}))
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
dragCallback = (node: D3ForceNode, x: number, y: number) => {
|
||||||
|
const newNode = [...this.state.nodes];
|
||||||
|
const nd = newNode.find(n => n.raw.getID() === node.props.id);
|
||||||
|
nd!.fx = x;
|
||||||
|
nd!.fy = y;
|
||||||
|
this.setState({
|
||||||
|
nodes: newNode
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.simulation.force('link', d3.forceLink().id((d: D3Node<N>) => d.raw.getID()));
|
||||||
|
|
||||||
|
this.updateNodes(this.props);
|
||||||
|
|
||||||
|
this.updateLinks(this.props);
|
||||||
|
|
||||||
|
this.simulation.nodes(this.nodes);
|
||||||
|
|
||||||
|
this.simulation.on('tick', () => {
|
||||||
|
if (this.canvas) {
|
||||||
|
this.links = this.links.map(this.updateRelation);
|
||||||
|
this.setState({
|
||||||
|
nodes: this.simulation.nodes(),
|
||||||
|
links: this.links
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.svg = d3.select<SVGSVGElement, {}>(`#${this.props.id}`)
|
||||||
|
.style('width', '100%')
|
||||||
|
.style('height', '800px')
|
||||||
|
.call(d3.zoom().on('zoom', () => {
|
||||||
|
let scale = d3.event.transform.k;
|
||||||
|
const {x, y} = d3.event.transform;
|
||||||
|
|
||||||
|
this.svg.attr('transform', `translate(${x}, ${y}) scale(${scale})`);
|
||||||
|
}))
|
||||||
|
.on('dblclick.zoom', null)
|
||||||
|
.select<SVGGElement>('g')
|
||||||
|
.attr('width', '100%')
|
||||||
|
.attr('height', '100%');
|
||||||
|
|
||||||
|
this.simulation.force<ForceLink<D3Node<N>, D3Relation<N, R>>>('link')!
|
||||||
|
.links(this.links)
|
||||||
|
.strength(0.03);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps(nextProps: Readonly<D3ForceProps<N, R>>) {
|
||||||
|
this.updateNodes(nextProps);
|
||||||
|
|
||||||
|
this.updateLinks(nextProps);
|
||||||
|
|
||||||
|
this.simulation.nodes(this.nodes);
|
||||||
|
|
||||||
|
this.simulation.force<ForceLink<D3Node<N>, D3Relation<N, R>>>('link')!
|
||||||
|
.links(this.links)
|
||||||
|
.strength(0.03);
|
||||||
|
|
||||||
|
this.simulation.alpha(1).restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {getNodeColor, getNodeLabel, getNodeText, getLinkID, getLinkText, onNodeClick, highlight} = this.props;
|
||||||
|
|
||||||
|
const sum = nodeRadius + arrowSize;
|
||||||
|
const half = sum / 2;
|
||||||
|
const up = half - arrowSize / 2;
|
||||||
|
const down = half + arrowSize / 2;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{width: '100%'}}>
|
||||||
|
<svg viewBox="-400,-400,800,800" id={this.props.id}>
|
||||||
|
<defs>
|
||||||
|
<marker
|
||||||
|
id="start-arrow"
|
||||||
|
markerWidth={sum}
|
||||||
|
markerHeight={sum}
|
||||||
|
refX={0}
|
||||||
|
refY={half}
|
||||||
|
orient="auto"
|
||||||
|
markerUnits="userSpaceOnUse"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d={`M${sum},${up} L${sum},${down} L${nodeRadius},${down} L${nodeRadius},${up} z`}
|
||||||
|
fill="#FFFFFF"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d={`M${sum},${up} L${sum},${down} L${nodeRadius},${half} z`}
|
||||||
|
fill="#000000"
|
||||||
|
/>
|
||||||
|
</marker>
|
||||||
|
<marker
|
||||||
|
id="end-arrow"
|
||||||
|
markerWidth={sum}
|
||||||
|
markerHeight={sum}
|
||||||
|
refX={sum}
|
||||||
|
refY={half}
|
||||||
|
orient="auto"
|
||||||
|
markerUnits="userSpaceOnUse"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d={`M0,${up} L0,${down} L${arrowSize},${down} L${arrowSize},${up} z`}
|
||||||
|
fill="#FFFFFF"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d={`M0,${up} L0,${down} L${arrowSize},${half} z`}
|
||||||
|
fill="#000000"
|
||||||
|
/>
|
||||||
|
</marker>
|
||||||
|
</defs>
|
||||||
|
<g ref={g => this.canvas = g}>
|
||||||
|
<g className="links">
|
||||||
|
{this.state.links.map(l => {
|
||||||
|
return (
|
||||||
|
<D3ForceLink
|
||||||
|
key={getLinkID(l.raw)}
|
||||||
|
nodeRadius={nodeRadius}
|
||||||
|
id={getLinkID(l.raw)}
|
||||||
|
text={getLinkText(l.raw)}
|
||||||
|
x1={l.x1}
|
||||||
|
x2={l.x2}
|
||||||
|
y1={l.y1}
|
||||||
|
y2={l.y2}
|
||||||
|
toSelf={l.source === l.target}
|
||||||
|
type={l.type}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</g>
|
||||||
|
<g className="nodes">
|
||||||
|
{this.state.nodes.map(n => (
|
||||||
|
<D3ForceNode
|
||||||
|
x={n.x}
|
||||||
|
y={n.y}
|
||||||
|
key={n.raw.getID()}
|
||||||
|
nodeRadius={nodeRadius}
|
||||||
|
id={n.raw.getID()}
|
||||||
|
color={getNodeColor(n.raw)}
|
||||||
|
label={getNodeLabel(n.raw)}
|
||||||
|
text={getNodeText(n.raw)}
|
||||||
|
simulation={this.simulation}
|
||||||
|
dragCallback={this.dragCallback}
|
||||||
|
onNodeClick={onNodeClick}
|
||||||
|
highlight={highlight.exists(x => x.toString() === n.raw.getID())}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default D3Force;
|
|
@ -0,0 +1,73 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
interface D3ForceLinkProps {
|
||||||
|
id: string;
|
||||||
|
text: string;
|
||||||
|
x1: number;
|
||||||
|
y1: number;
|
||||||
|
x2: number;
|
||||||
|
y2: number;
|
||||||
|
nodeRadius: number;
|
||||||
|
toSelf: boolean;
|
||||||
|
type: 'single' | 'repeated';
|
||||||
|
}
|
||||||
|
|
||||||
|
class D3ForceLink extends React.Component<D3ForceLinkProps, {}> {
|
||||||
|
|
||||||
|
g: SVGGElement | null;
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {id, text, x1, y1, x2, y2, type, toSelf, nodeRadius} = this.props;
|
||||||
|
|
||||||
|
let d = '';
|
||||||
|
|
||||||
|
if (type === 'single') {
|
||||||
|
d = x1 > x2 ? `M${x2},${y2} L${x1},${y1}` : `M${x1},${y1} L${x2},${y2}`;
|
||||||
|
} else if (toSelf) {
|
||||||
|
d = `M${x1 - nodeRadius},${y1}
|
||||||
|
A${nodeRadius},${nodeRadius} 0 1,0
|
||||||
|
${x1},${y1 + nodeRadius}`;
|
||||||
|
} else {
|
||||||
|
const dx = x2 - x1;
|
||||||
|
const dy = y2 - y1;
|
||||||
|
const dxx = dx * dx;
|
||||||
|
const dyy = dy * dy;
|
||||||
|
const dxy = dx * dy;
|
||||||
|
const sx = x1 + x2;
|
||||||
|
const sy = y1 + y2;
|
||||||
|
const dis = Math.sqrt(dxx + dyy);
|
||||||
|
|
||||||
|
const fx =
|
||||||
|
(-dxy * y1 + dyy * x1 + 0.5 * dxx * sx + 0.5 * dxy * sy - dis / 5 * dis * dy) / (dxx + dyy);
|
||||||
|
const fy =
|
||||||
|
(dxx * y1 - dxy * x1 + 0.5 * dxy * sx + 0.5 * dyy * sy + dis / 5 * dis * dx) / (dxx + dyy);
|
||||||
|
d = x1 > x2 ?
|
||||||
|
`M${x2},${y2} Q${fx},${fy} ${x1},${y1}` : `M${x1},${y1} Q${fx},${fy} ${x2},${y2}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const markerStart = x1 > x2 ? 'url(#start-arrow)' : '';
|
||||||
|
const markerEnd = x2 > x1 ? 'url(#end-arrow)' : '';
|
||||||
|
|
||||||
|
return (
|
||||||
|
<g ref={x => this.g = x} className="link">
|
||||||
|
<path
|
||||||
|
id={`p${id}`}
|
||||||
|
className="link"
|
||||||
|
fill="none"
|
||||||
|
stroke="black"
|
||||||
|
strokeWidth={3}
|
||||||
|
d={d}
|
||||||
|
markerStart={markerStart}
|
||||||
|
markerEnd={markerEnd}
|
||||||
|
/>
|
||||||
|
<text transform="translate(0,-2)" className="link-label" textAnchor="middle">
|
||||||
|
<textPath xlinkHref={`#p${id}`} startOffset="50%">
|
||||||
|
{text}
|
||||||
|
</textPath>
|
||||||
|
</text>
|
||||||
|
</g>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default D3ForceLink;
|
|
@ -0,0 +1,67 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import * as d3 from 'd3';
|
||||||
|
|
||||||
|
interface D3ForceNodeProps {
|
||||||
|
nodeRadius: number;
|
||||||
|
id: string;
|
||||||
|
color: string;
|
||||||
|
label: string;
|
||||||
|
text: string;
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
simulation: d3.Simulation<{}, undefined>;
|
||||||
|
dragCallback: (node: D3ForceNode, x: number, y: number) => void;
|
||||||
|
highlight?: boolean;
|
||||||
|
onNodeClick?: (id: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
class D3ForceNode extends React.Component<D3ForceNodeProps, {}> {
|
||||||
|
|
||||||
|
g: SVGGElement | null;
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
const {simulation, dragCallback, onNodeClick, id} = this.props;
|
||||||
|
|
||||||
|
const node = d3.select(this.g)
|
||||||
|
.call(d3.drag<SVGGElement, {}>()
|
||||||
|
.on('start', () => {
|
||||||
|
if (!d3.event.active) {
|
||||||
|
simulation.alphaTarget(0.3).restart();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.on('drag', () => {
|
||||||
|
dragCallback(this, d3.event.x, d3.event.y);
|
||||||
|
})
|
||||||
|
.on('end', () => {
|
||||||
|
if (!d3.event.active) {
|
||||||
|
simulation.alphaTarget(0);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
if (onNodeClick) {
|
||||||
|
node.on('click', () => onNodeClick(id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {nodeRadius, color, label, text, x, y, highlight} = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<g ref={i => this.g = i} className="node" transform={`translate(${x}, ${y})`}>
|
||||||
|
<circle
|
||||||
|
r={nodeRadius}
|
||||||
|
cx={0}
|
||||||
|
cy={0}
|
||||||
|
style={{fill: color}}
|
||||||
|
strokeWidth={highlight ? 3 : 0}
|
||||||
|
stroke="black"
|
||||||
|
/>
|
||||||
|
<text textAnchor="middle" x={0} y={-5} fontWeight="bold">{label}</text>
|
||||||
|
<text textAnchor="middle" x={0} y={15}>{text}</text>
|
||||||
|
</g>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default D3ForceNode;
|
|
@ -0,0 +1,21 @@
|
||||||
|
export const SERVER_URL = 'http://106.75.143.22:8004';
|
||||||
|
export const PROJECTS_INFO_URL = `${SERVER_URL}/projects`;
|
||||||
|
export const DOCUMENT_SEARCH_URL = `${SERVER_URL}/docSearch`;
|
||||||
|
export const CODE_SEARCH_URL = `${SERVER_URL}/codeSearch`;
|
||||||
|
export const NODE_INFO_URL = `${SERVER_URL}/node`;
|
||||||
|
export const RELATION_LIST_URL = `${SERVER_URL}/relationList`;
|
||||||
|
export const NAV_URL = `${SERVER_URL}/nav`;
|
||||||
|
|
||||||
|
export const DOC_PREDEFINED_QUERIES = [
|
||||||
|
'How to write a document into an index?',
|
||||||
|
'How to get all field names in an IndexReader?',
|
||||||
|
'How to parse wildcard query using TokenFilter?',
|
||||||
|
];
|
||||||
|
|
||||||
|
export const GRAPH_PREDEFINED_QUERIES = [
|
||||||
|
'Who change class named IndexReader',
|
||||||
|
'How to write a document into an index?',
|
||||||
|
'How to get all field names in an IndexReader?',
|
||||||
|
'How to parse wildcard query using TokenFilter?',
|
||||||
|
'list method of Class IndexWriter'
|
||||||
|
];
|
|
@ -0,0 +1,26 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import * as ReactDOM from 'react-dom';
|
||||||
|
import App from './App';
|
||||||
|
import registerServiceWorker from './registerServiceWorker';
|
||||||
|
import { Provider } from 'react-redux';
|
||||||
|
import { applyMiddleware, createStore } from 'redux';
|
||||||
|
import thunk from 'redux-thunk';
|
||||||
|
import { appReducer } from './redux/reducer';
|
||||||
|
import { composeWithDevTools } from 'redux-devtools-extension';
|
||||||
|
import 'typeface-roboto';
|
||||||
|
import { BrowserRouter } from 'react-router-dom';
|
||||||
|
|
||||||
|
require('../node_modules/js-snackbar/dist/snackbar.css');
|
||||||
|
require('./assets/css/snowgraph.css');
|
||||||
|
|
||||||
|
const store = createStore(appReducer, composeWithDevTools(applyMiddleware(thunk)));
|
||||||
|
|
||||||
|
ReactDOM.render(
|
||||||
|
<Provider store={store}>
|
||||||
|
<BrowserRouter>
|
||||||
|
<App/>
|
||||||
|
</BrowserRouter>
|
||||||
|
</Provider>,
|
||||||
|
document.getElementById('root')
|
||||||
|
);
|
||||||
|
registerServiceWorker();
|
|
@ -0,0 +1,12 @@
|
||||||
|
|
||||||
|
declare module 'js-snackbar' {
|
||||||
|
type ShowOption = {
|
||||||
|
text: string;
|
||||||
|
pos?: 'bottom-left' | 'bottom-center' | 'bottom-right' | 'top-left' | 'top-center' | 'top-right';
|
||||||
|
duration: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function show(param: ShowOption): void
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
export interface CypherQueryResult {
|
||||||
|
cypher?: string;
|
||||||
|
nodes: Neo4jNode[];
|
||||||
|
relationships: Neo4jRelation[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NavResult {
|
||||||
|
nodes: NavNode[];
|
||||||
|
relationships: NavRelation[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NavRelation {
|
||||||
|
startNode: number;
|
||||||
|
endNode: number;
|
||||||
|
id: number;
|
||||||
|
count: number;
|
||||||
|
type: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NavNode {
|
||||||
|
id: number;
|
||||||
|
label: string;
|
||||||
|
count: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Neo4jRelation {
|
||||||
|
startNode: number;
|
||||||
|
endNode: number;
|
||||||
|
id: number;
|
||||||
|
type: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Neo4jNode {
|
||||||
|
id: number;
|
||||||
|
label: string;
|
||||||
|
properties: { [key: string]: any; };
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SnowRelation {
|
||||||
|
shown: boolean;
|
||||||
|
id: string;
|
||||||
|
source: number;
|
||||||
|
target: number;
|
||||||
|
types: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SnowNode implements INode {
|
||||||
|
shown: boolean;
|
||||||
|
node: Neo4jNode;
|
||||||
|
|
||||||
|
constructor(shown: boolean, node: Neo4jNode) {
|
||||||
|
this.shown = shown;
|
||||||
|
this.node = node;
|
||||||
|
}
|
||||||
|
|
||||||
|
getID(): string {
|
||||||
|
return this.node.id.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface INode {
|
||||||
|
getID(): string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DocumentProperty {
|
||||||
|
content: string;
|
||||||
|
html: string;
|
||||||
|
id: number;
|
||||||
|
label: string;
|
||||||
|
title: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DocumentResult {
|
||||||
|
rank: number;
|
||||||
|
id: number;
|
||||||
|
label: string;
|
||||||
|
properties: DocumentProperty;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProjectInfo {
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
}
|
|
@ -0,0 +1,97 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import RegularCard from '../components/Cards/RegularCard';
|
||||||
|
import MatTable from '../components/MatTable/MatTable';
|
||||||
|
import { withStyles, WithStyles } from 'material-ui';
|
||||||
|
import { container } from '../variables/styles';
|
||||||
|
import { StyleRules } from 'material-ui/styles';
|
||||||
|
|
||||||
|
type AboutPageStyleStyleKeys = 'container';
|
||||||
|
|
||||||
|
type AboutPageStyle = WithStyles<AboutPageStyleStyleKeys>;
|
||||||
|
|
||||||
|
const styles = () => ({
|
||||||
|
container: {
|
||||||
|
paddingTop: 40,
|
||||||
|
...container
|
||||||
|
}
|
||||||
|
}) as StyleRules<AboutPageStyleStyleKeys>;
|
||||||
|
|
||||||
|
const faculty = [{
|
||||||
|
key: 'Bing Xie',
|
||||||
|
columns: ['Bing Xie', 'Professor, Peking University'],
|
||||||
|
}, {
|
||||||
|
key: 'Yanzhen Zou',
|
||||||
|
columns: ['Yanzhen Zou', 'Associated Professor, Peking University'],
|
||||||
|
}, {
|
||||||
|
key: 'Junfeng Zhao',
|
||||||
|
columns: ['Junfeng Zhao', 'Associated Professor, Peking University'],
|
||||||
|
}];
|
||||||
|
|
||||||
|
const projectManager = [{
|
||||||
|
key: 'Zeqi Lin',
|
||||||
|
columns: ['Zeqi Lin', 'PhD Student, Peking University'],
|
||||||
|
}, {
|
||||||
|
key: 'Min Wang',
|
||||||
|
columns: ['Min Wang', 'PhD Student, Peking University'],
|
||||||
|
}];
|
||||||
|
|
||||||
|
const committers = [{
|
||||||
|
key: 'Jinan Ni',
|
||||||
|
columns: ['Jinan Ni', 'Master Student, Peking University'],
|
||||||
|
}, {
|
||||||
|
key: 'Yingkui Cao',
|
||||||
|
columns: ['Yingkui Cao', 'PhD Student, Peking University'],
|
||||||
|
}, {
|
||||||
|
key: 'Chenyan Hua',
|
||||||
|
columns: ['Chenyan Hua', 'Master Student, Peking University'],
|
||||||
|
}, {
|
||||||
|
key: 'Qi Shen',
|
||||||
|
columns: ['Qi Shen', 'PhD Student, Peking University'],
|
||||||
|
}, {
|
||||||
|
key: 'Chunyang Ling',
|
||||||
|
columns: ['Chunyang Ling', 'Master Student, Peking University'],
|
||||||
|
}, {
|
||||||
|
key: 'Wenpeng Li',
|
||||||
|
columns: ['Wenpeng Li', 'Master Student, Peking University'],
|
||||||
|
}, {
|
||||||
|
key: 'Ying Qian',
|
||||||
|
columns: ['Ying Qian', 'Master Student, Peking University'],
|
||||||
|
}];
|
||||||
|
|
||||||
|
const sponsors = [
|
||||||
|
{
|
||||||
|
columns: [`This project is supported by the National Key Research and Development Program of China\
|
||||||
|
(Grant No. 2016YFB1000801).`]
|
||||||
|
}, {
|
||||||
|
columns: [`The computing resource is supported by the National Key Research and Development Program of China\
|
||||||
|
(Grant No. 2016YFB1000805).`],
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
class AboutPage extends React.Component<AboutPageStyle, {}> {
|
||||||
|
render() {
|
||||||
|
const {classes} = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classes.container}>
|
||||||
|
<RegularCard cardTitle="Authors" headerColor="blue">
|
||||||
|
<p>
|
||||||
|
SnowGraph由北京大学信息科学技术学院软件复用小组研发。
|
||||||
|
该小组以谢冰教授为核心,主要研究内容涵盖软件工程、软件复用技术的多个方面,包括:软件构建技术、软件资源管理、软件数据挖掘、智能软件开发方法等。
|
||||||
|
SnowGraph的主要研发人员包括(2016年至今):
|
||||||
|
</p>
|
||||||
|
<h5>Faculty:</h5>
|
||||||
|
<MatTable tableData={faculty}/>
|
||||||
|
<h5>Project Manager:</h5>
|
||||||
|
<MatTable tableData={projectManager}/>
|
||||||
|
<h5>Committers:</h5>
|
||||||
|
<MatTable tableData={committers}/>
|
||||||
|
<h5>Sponsors:</h5>
|
||||||
|
<MatTable tableData={sponsors}/>
|
||||||
|
</RegularCard>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withStyles(styles)<{}>(AboutPage);
|
|
@ -0,0 +1,85 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import logo from '../../assets/img/logo.png';
|
||||||
|
import { TextField, WithStyles } from 'material-ui';
|
||||||
|
import { withStyles } from 'material-ui/styles';
|
||||||
|
import { StyleRules } from 'material-ui/styles/withStyles';
|
||||||
|
import { ChangeEvent } from 'react';
|
||||||
|
import { ProjectInfo } from '../../model';
|
||||||
|
import { PROJECTS_INFO_URL } from '../../config';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import MatTable from '../../components/MatTable/MatTable';
|
||||||
|
import { container, flexContainer } from '../../variables/styles';
|
||||||
|
import RegularCard from '../../components/Cards/RegularCard';
|
||||||
|
|
||||||
|
interface CatalogPageState {
|
||||||
|
filter: string;
|
||||||
|
projects: ProjectInfo[];
|
||||||
|
}
|
||||||
|
|
||||||
|
type CatalogPageStyleKeys = 'container' | 'flexContainer' | 'search';
|
||||||
|
|
||||||
|
type CatalogPageStyle = WithStyles<CatalogPageStyleKeys>;
|
||||||
|
|
||||||
|
const styles = () => ({
|
||||||
|
container,
|
||||||
|
flexContainer,
|
||||||
|
search: {
|
||||||
|
width: '100%'
|
||||||
|
},
|
||||||
|
}) as StyleRules<CatalogPageStyleKeys>;
|
||||||
|
|
||||||
|
class CatalogPage extends React.Component<CatalogPageStyle, CatalogPageState> {
|
||||||
|
constructor(props: CatalogPageStyle) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
filter: '',
|
||||||
|
projects: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
fetch(PROJECTS_INFO_URL)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(r => {
|
||||||
|
this.setState({projects: r});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleFilterChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
this.setState({filter: event.target.value});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {classes} = this.props;
|
||||||
|
const {filter, projects} = this.state;
|
||||||
|
|
||||||
|
const data = projects.filter(p => p.name.toLowerCase().includes(filter.toLowerCase())).map(p => ({
|
||||||
|
key: p.name,
|
||||||
|
columns: [
|
||||||
|
<Link key={p.name} to={`/demo/${p.name}/diagram`}> {p.name} </Link>,
|
||||||
|
p.description
|
||||||
|
]
|
||||||
|
}));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classes.container}>
|
||||||
|
<div className={classes.flexContainer}>
|
||||||
|
<img src={logo} alt="logo"/>
|
||||||
|
<RegularCard>
|
||||||
|
<TextField
|
||||||
|
id="search"
|
||||||
|
label="Filter Projects"
|
||||||
|
type="search"
|
||||||
|
className={classes.search}
|
||||||
|
onChange={this.handleFilterChange}
|
||||||
|
/>
|
||||||
|
<MatTable tableHead={['项目', '简介']} tableData={data}/>
|
||||||
|
</RegularCard>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withStyles(styles)<{}>(CatalogPage);
|
|
@ -0,0 +1,19 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import { Route, Switch } from 'react-router-dom';
|
||||||
|
import CatalogPage from './CatalogPage';
|
||||||
|
import ProjectPage from './ProjectPage/ProjectPage';
|
||||||
|
|
||||||
|
class DemoPage extends React.Component<{}, {}> {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Switch>
|
||||||
|
<Route exact={true} path="/demo" component={CatalogPage}/>
|
||||||
|
<Route path="/demo/:project/:tab" component={ProjectPage}/>
|
||||||
|
</Switch>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DemoPage;
|
|
@ -0,0 +1,151 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import * as $ from 'jquery';
|
||||||
|
import { Component } from 'react';
|
||||||
|
import { Dispatch } from 'redux';
|
||||||
|
import { RouteComponentProps, withRouter } from 'react-router';
|
||||||
|
import { LinearProgress, Typography, withStyles, WithStyles } from 'material-ui';
|
||||||
|
import { History } from 'history';
|
||||||
|
import { RootState } from '../../../redux/reducer';
|
||||||
|
import D3Chord from '../../../components/D3Chord';
|
||||||
|
import { name2color } from '../../../utils/utils';
|
||||||
|
import Grid from 'material-ui/Grid';
|
||||||
|
import Statistic from '../../../components/Statistic';
|
||||||
|
import { container } from '../../../variables/styles';
|
||||||
|
import { StyleRules } from 'material-ui/styles';
|
||||||
|
import { NAV_URL } from '../../../config';
|
||||||
|
import { NavNode, NavRelation, NavResult } from '../../../model';
|
||||||
|
import { none, Option, some } from 'ts-option';
|
||||||
|
|
||||||
|
interface DiagramTabState {
|
||||||
|
navGraph: {
|
||||||
|
fetching: boolean;
|
||||||
|
nodes: NavNode[];
|
||||||
|
relations: NavRelation[];
|
||||||
|
matrix: Option<number[][]>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DiagramTabRouteProps {
|
||||||
|
project: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DiagramTabProps extends RouteComponentProps<DiagramTabRouteProps> {
|
||||||
|
dispatch: Dispatch<RootState>;
|
||||||
|
history: History;
|
||||||
|
}
|
||||||
|
|
||||||
|
type DiagramTabStyleKeys = 'container' | 'white' | 'progress' | 'info';
|
||||||
|
|
||||||
|
type DiagramTabStyles = WithStyles<DiagramTabStyleKeys>;
|
||||||
|
|
||||||
|
const styles = () => ({
|
||||||
|
container: {
|
||||||
|
paddingTop: '60px',
|
||||||
|
...container
|
||||||
|
},
|
||||||
|
info: {
|
||||||
|
fontSize: '1.8rem',
|
||||||
|
margin: '20px',
|
||||||
|
fontWeight: "bold"
|
||||||
|
}
|
||||||
|
}) as StyleRules<DiagramTabStyleKeys>;
|
||||||
|
|
||||||
|
class DiagramTab extends Component<DiagramTabProps & DiagramTabStyles, DiagramTabState> {
|
||||||
|
state: DiagramTabState = {
|
||||||
|
navGraph: {
|
||||||
|
fetching: false,
|
||||||
|
nodes: [],
|
||||||
|
relations: [],
|
||||||
|
matrix: none
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
changeFetch = (fetching: boolean) => {
|
||||||
|
this.setState(prevState => ({
|
||||||
|
navGraph: Object.assign(prevState, {fetching})
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
resultToState = (result: NavResult) => {
|
||||||
|
const ns = result.nodes;
|
||||||
|
const d = ns.map(() => ns.map(() => 0));
|
||||||
|
result.relationships.forEach(r => d[r.startNode][r.endNode] += r.count);
|
||||||
|
|
||||||
|
return {
|
||||||
|
navGraph: {
|
||||||
|
fetching: false,
|
||||||
|
nodes: result.nodes,
|
||||||
|
relations: result.relationships,
|
||||||
|
matrix: some(d)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.changeFetch(true);
|
||||||
|
|
||||||
|
$.post(NAV_URL, {project: this.props.match.params.project})
|
||||||
|
.then((result: NavResult) => this.setState(this.resultToState(result)))
|
||||||
|
.catch(() => this.changeFetch(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {classes} = this.props;
|
||||||
|
const {navGraph} = this.state;
|
||||||
|
|
||||||
|
let navBody = <LinearProgress className={classes.progress}/>;
|
||||||
|
|
||||||
|
if (!navGraph.fetching) {
|
||||||
|
let numberNodes = 0 ;
|
||||||
|
let numberEdges = 0 ;
|
||||||
|
navGraph.nodes.map( n=>{ numberNodes+=n.count});
|
||||||
|
navGraph.relations.map(relation =>{numberEdges+=relation.count})
|
||||||
|
|
||||||
|
navBody = navGraph.matrix.isEmpty ?
|
||||||
|
<Typography className={classes.white}>Failed to load nav graph</Typography> : (
|
||||||
|
<Grid container={true} spacing={8}>
|
||||||
|
<Grid item={true} xs={5}>
|
||||||
|
<D3Chord
|
||||||
|
id="nav-d3"
|
||||||
|
data={navGraph.matrix.get}
|
||||||
|
colors={navGraph.nodes.map(n => name2color(n.label))}
|
||||||
|
labels={navGraph.nodes.map(n => `${n.label}(${n.count})`)}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item={true} xs={7}>
|
||||||
|
<Typography className={classes.info}>Nodes: {numberNodes}</Typography>
|
||||||
|
<Grid container={true} spacing={8}>
|
||||||
|
{navGraph.nodes.map(node => (
|
||||||
|
<Grid style={{textAlign: 'center'}} key={node.label} item={true} lg={3} sm={4}>
|
||||||
|
<Statistic num={node.count} label={node.label}/>
|
||||||
|
</Grid>
|
||||||
|
))}
|
||||||
|
</Grid>
|
||||||
|
<Typography className={classes.info}>Edges: {numberEdges}</Typography>
|
||||||
|
<Grid container={true} spacing={8}>
|
||||||
|
{navGraph.relations.map(relation => (
|
||||||
|
<Grid
|
||||||
|
style={{textAlign: 'center'}}
|
||||||
|
key={`${relation.startNode}-${relation.endNode}-${relation.type}`}
|
||||||
|
item={true}
|
||||||
|
lg={3}
|
||||||
|
sm={4}
|
||||||
|
>
|
||||||
|
<Statistic num={relation.count} label={relation.type}/>
|
||||||
|
</Grid>
|
||||||
|
))}
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classes.container}>
|
||||||
|
{navBody}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withStyles(styles)<{}>(withRouter(DiagramTab));
|
|
@ -0,0 +1,71 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import { LinearProgress, withStyles, WithStyles } from 'material-ui';
|
||||||
|
import { Theme } from 'material-ui/styles';
|
||||||
|
import { DocumentResultState, RootState } from '../../../redux/reducer';
|
||||||
|
import RankRow from '../../../components/RankRow';
|
||||||
|
import SearchForm from '../../../components/SearchForm';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { fetchDocumentResultWorker } from '../../../redux/action';
|
||||||
|
import { DOC_PREDEFINED_QUERIES } from '../../../config';
|
||||||
|
import { RouteComponentProps } from 'react-router';
|
||||||
|
import MatTable from '../../../components/MatTable/MatTable';
|
||||||
|
import { container } from '../../../variables/styles';
|
||||||
|
|
||||||
|
const styles = (theme: Theme) => ({
|
||||||
|
container: {
|
||||||
|
...container,
|
||||||
|
},
|
||||||
|
progress: {
|
||||||
|
flexGrow: 1,
|
||||||
|
margin: theme.spacing.unit * 4
|
||||||
|
}
|
||||||
|
}) as React.CSSProperties;
|
||||||
|
|
||||||
|
const mapStateToProps = (state: RootState) => ({
|
||||||
|
documentResult: state.documentResult
|
||||||
|
});
|
||||||
|
|
||||||
|
interface DocumentTabRouteProps {
|
||||||
|
project: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DocumentTabProps extends RouteComponentProps<DocumentTabRouteProps> {
|
||||||
|
documentResult: DocumentResultState;
|
||||||
|
}
|
||||||
|
|
||||||
|
type DocumentTabStyle = WithStyles<'container' | 'progress'>;
|
||||||
|
|
||||||
|
class DocumentTab extends React.Component<DocumentTabProps & DocumentTabStyle, {}> {
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {classes, documentResult} = this.props;
|
||||||
|
const project = this.props.match.params.project;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<SearchForm
|
||||||
|
query={documentResult.query}
|
||||||
|
predefinedQueries={DOC_PREDEFINED_QUERIES}
|
||||||
|
callback={(param: { query: string }) => fetchDocumentResultWorker({project, query: param.query})}
|
||||||
|
/>
|
||||||
|
<div className={classes.container}>
|
||||||
|
{documentResult.fetching && <LinearProgress className={classes.progress}/>}
|
||||||
|
{documentResult.result != null &&
|
||||||
|
<MatTable
|
||||||
|
tableHead={['Rank', 'ID', 'Answer']}
|
||||||
|
tableData={documentResult.result.map(r => ({
|
||||||
|
columns: [`${r.rank}`, `${r.id}`, <RankRow
|
||||||
|
key={r.id}
|
||||||
|
initExpand={r.rank === 1}
|
||||||
|
title={'[' + r.label + '] ' + (r.properties.title == null ? "" : r.properties.title)}
|
||||||
|
detail={r.properties.html}
|
||||||
|
/>]
|
||||||
|
}))}
|
||||||
|
/>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withStyles(styles)<{}>(connect(mapStateToProps)(DocumentTab));
|
|
@ -0,0 +1,119 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import {
|
||||||
|
InputLabel, FormControl, LinearProgress, withStyles,
|
||||||
|
WithStyles, FormGroup
|
||||||
|
} from 'material-ui';
|
||||||
|
import { Theme } from 'material-ui/styles';
|
||||||
|
import { RootState } from '../../../redux/reducer';
|
||||||
|
import { Dispatch } from 'redux';
|
||||||
|
import Select from 'material-ui/Select';
|
||||||
|
import Input from 'material-ui/Input';
|
||||||
|
import Button from 'material-ui/Button';
|
||||||
|
import Typography from 'material-ui/Typography';
|
||||||
|
import { FormEvent } from 'react';
|
||||||
|
import { Option } from 'ts-option';
|
||||||
|
import * as _ from 'lodash';
|
||||||
|
import { fetchNodeWorker, removeNode, showRelations } from '../../../redux/action';
|
||||||
|
import { RelationListsState, RelationsState } from '../../../redux/graphReducer';
|
||||||
|
import { Chance } from 'chance';
|
||||||
|
import RegularCard from '../../../components/Cards/RegularCard';
|
||||||
|
|
||||||
|
const chance = new Chance();
|
||||||
|
|
||||||
|
const styles = (theme: Theme) => ({
|
||||||
|
formControl: {
|
||||||
|
margin: theme.spacing.unit,
|
||||||
|
minWidth: 240,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapStateToProps = (state: RootState) => ({
|
||||||
|
selectedNode: state.graph.selectedNode,
|
||||||
|
relations: state.graph.relations,
|
||||||
|
relationLists: state.graph.relationLists,
|
||||||
|
});
|
||||||
|
|
||||||
|
interface FindEntityPanelProps {
|
||||||
|
selectedNode: Option<number>;
|
||||||
|
relations: RelationsState;
|
||||||
|
relationLists: RelationListsState;
|
||||||
|
dispatch: Dispatch<RootState>;
|
||||||
|
project: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
class FindEntityPanel extends React.Component<FindEntityPanelProps & WithStyles<'formControl'>, {}> {
|
||||||
|
input: HTMLInputElement;
|
||||||
|
|
||||||
|
handleSubmit = (event: FormEvent<HTMLFormElement>) => {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const catalog = this.input.value;
|
||||||
|
const {dispatch, selectedNode, relationLists, relations, project} = this.props;
|
||||||
|
|
||||||
|
const relationList = relationLists.get(selectedNode.get);
|
||||||
|
|
||||||
|
const rels = relationList.get
|
||||||
|
.map(x => relations.get(x))
|
||||||
|
.filter(x => !x.shown)
|
||||||
|
.filter(x => x.types.some(t => t === catalog));
|
||||||
|
|
||||||
|
const readyToShow = rels.length > 0 ? chance.pickset(rels, 5) : [];
|
||||||
|
|
||||||
|
readyToShow.forEach(r => {
|
||||||
|
const source = r.source, target = r.target;
|
||||||
|
const otherID = source === selectedNode.get ? target : source;
|
||||||
|
dispatch(fetchNodeWorker({project, id: otherID}));
|
||||||
|
});
|
||||||
|
|
||||||
|
dispatch(showRelations(readyToShow.map(x => x.id)));
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
let body = null;
|
||||||
|
|
||||||
|
const {dispatch, selectedNode, relationLists, relations} = this.props;
|
||||||
|
|
||||||
|
if (selectedNode.isEmpty) {
|
||||||
|
body = <Typography component="p"> No entity selected. </Typography>;
|
||||||
|
} else {
|
||||||
|
const selected = selectedNode.get;
|
||||||
|
const selectedRelationList = relationLists.get(selected);
|
||||||
|
|
||||||
|
if (selectedRelationList.isEmpty) {
|
||||||
|
body = <LinearProgress/>;
|
||||||
|
} else {
|
||||||
|
const relationTypes = _.chain(selectedRelationList.get)
|
||||||
|
.flatMap(x => relations.get(x).types)
|
||||||
|
.uniq()
|
||||||
|
.value();
|
||||||
|
body = (
|
||||||
|
<form onSubmit={this.handleSubmit}>
|
||||||
|
<FormGroup>
|
||||||
|
<FormControl className={this.props.classes.formControl}>
|
||||||
|
<InputLabel htmlFor="relation-type">Relation Type</InputLabel>
|
||||||
|
<Select
|
||||||
|
native={true}
|
||||||
|
input={<Input id="relation-type" inputRef={(input) => this.input = input}/>}
|
||||||
|
>
|
||||||
|
{relationTypes.map(t => <option key={t} value={t}>{t}</option>)}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
</FormGroup>
|
||||||
|
<Button type="submit">EXPAND</Button>
|
||||||
|
<Button onClick={() => dispatch(removeNode(selected))}>HIDE</Button>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RegularCard headerColor="blue" cardTitle="Surf in the knowledge graph">
|
||||||
|
{body}
|
||||||
|
</RegularCard>
|
||||||
|
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withStyles(styles)<{ project: string }>(connect(mapStateToProps)(FindEntityPanel));
|
|
@ -0,0 +1,83 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { Dispatch } from 'redux';
|
||||||
|
import { RootState } from '../../../redux/reducer';
|
||||||
|
import D3Force from '../../../components/d3/D3Force';
|
||||||
|
import { Option } from 'ts-option';
|
||||||
|
import { SnowNode, SnowRelation } from '../../../model';
|
||||||
|
import { NodesState, RelationsState } from '../../../redux/graphReducer';
|
||||||
|
import { fetchRelationListWorker, selectNode } from '../../../redux/action';
|
||||||
|
import { name2color } from '../../../utils/utils';
|
||||||
|
import RegularCard from '../../../components/Cards/RegularCard';
|
||||||
|
|
||||||
|
const mapStateToProps = (state: RootState) => ({
|
||||||
|
nodes: state.graph.nodes,
|
||||||
|
relations: state.graph.relations,
|
||||||
|
selectedNode: state.graph.selectedNode
|
||||||
|
});
|
||||||
|
|
||||||
|
interface GraphPanelProps {
|
||||||
|
nodes: NodesState;
|
||||||
|
relations: RelationsState;
|
||||||
|
selectedNode: Option<number>;
|
||||||
|
dispatch: Dispatch<RootState>;
|
||||||
|
project: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Graph extends D3Force<SnowNode, SnowRelation> {
|
||||||
|
}
|
||||||
|
|
||||||
|
class GraphPanel extends React.Component<GraphPanelProps, {}> {
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {dispatch, selectedNode, project} = this.props;
|
||||||
|
|
||||||
|
const nodes = this.props.nodes
|
||||||
|
.valueSeq()
|
||||||
|
.flatMap<number, SnowNode>((x?: Option<SnowNode>) => x!.toArray)
|
||||||
|
.filter(x => x!.shown)
|
||||||
|
.toArray();
|
||||||
|
|
||||||
|
const links = this.props.relations
|
||||||
|
.valueSeq()
|
||||||
|
.filter(x => x!.shown)
|
||||||
|
.filter(x => nodes.some(n => n.node.id === x!.source) &&
|
||||||
|
nodes.some(n => n.node.id === x!.target))
|
||||||
|
.toArray();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RegularCard headerColor="blue" cardTitle="Knowledge Graph Inference Result">
|
||||||
|
{this.props.nodes.isEmpty() ?
|
||||||
|
'' :
|
||||||
|
<Graph
|
||||||
|
id="neo4jd3"
|
||||||
|
highlight={selectedNode}
|
||||||
|
nodes={nodes}
|
||||||
|
links={links}
|
||||||
|
getNodeColor={n => name2color(n.node.label)}
|
||||||
|
getNodeLabel={n => n.node.label}
|
||||||
|
getNodeText={n => {
|
||||||
|
let name = '';
|
||||||
|
name = n.node.properties['_names'] ? n.node.properties['_names'] : name;
|
||||||
|
name = n.node.properties['_title'] ? n.node.properties['_title'] : name;
|
||||||
|
name = n.node.properties['title'] ? n.node.properties['title'] : name;
|
||||||
|
name = n.node.properties['name'] ? n.node.properties['name'] : name;
|
||||||
|
name = name.replace(/<(?:.|\s)*?>/g, ' ').trim();
|
||||||
|
name = name.length > 10 ? name.substr(0, 8) + '...' : name;
|
||||||
|
return name;
|
||||||
|
}}
|
||||||
|
getLinkID={d => d.id}
|
||||||
|
getLinkText={d => d.types.toString()}
|
||||||
|
getSourceNodeID={d => d.source.toString()}
|
||||||
|
getTargetNodeID={d => d.target.toString()}
|
||||||
|
onNodeClick={id => {
|
||||||
|
dispatch(fetchRelationListWorker({project, id: parseInt(id, 10)}));
|
||||||
|
dispatch(selectNode(parseInt(id, 10)));
|
||||||
|
}}
|
||||||
|
/>}
|
||||||
|
</RegularCard>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps)(GraphPanel);
|
|
@ -0,0 +1,74 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import GraphPanel from './GraphPanel';
|
||||||
|
import FindEntityPanel from './FindEntityPanel';
|
||||||
|
import InformationPanel from './InformationPanel';
|
||||||
|
import { Grid, LinearProgress, withStyles, WithStyles } from 'material-ui';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { Theme } from 'material-ui/styles';
|
||||||
|
import { RootState } from '../../../redux/reducer';
|
||||||
|
import { Dispatch } from 'redux';
|
||||||
|
import RegularCard from '../../../components/Cards/RegularCard';
|
||||||
|
|
||||||
|
const styles = (theme: Theme) => ({
|
||||||
|
leftPanel: {
|
||||||
|
padding: theme.spacing.unit * 2,
|
||||||
|
},
|
||||||
|
rightPanel: {
|
||||||
|
paddingTop: theme.spacing.unit * 2,
|
||||||
|
paddingRight: theme.spacing.unit * 2,
|
||||||
|
},
|
||||||
|
informationPanel: {
|
||||||
|
marginTop: theme.spacing.unit * 2,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapStateToProps = (state: RootState) => {
|
||||||
|
return {
|
||||||
|
fetchingGraph: state.graph.fetching,
|
||||||
|
cypher: state.graph.cypher,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
interface GraphPanelsProps {
|
||||||
|
fetchingGraph: boolean;
|
||||||
|
cypher: string;
|
||||||
|
project: string;
|
||||||
|
dispatch: Dispatch<RootState>;
|
||||||
|
}
|
||||||
|
|
||||||
|
type GraphPanelsStyles = WithStyles<'leftPanel' | 'rightPanel' | 'informationPanel'>;
|
||||||
|
|
||||||
|
class GraphPanels extends React.Component<GraphPanelsProps & GraphPanelsStyles, {}> {
|
||||||
|
render() {
|
||||||
|
const {classes, fetchingGraph, project, cypher} = this.props;
|
||||||
|
const show = (
|
||||||
|
<Grid container={true} spacing={0}>
|
||||||
|
<Grid item={true} xs={12} className={classes.leftPanel}>
|
||||||
|
{cypher.length > 0 &&
|
||||||
|
<RegularCard headerColor="blue" cardTitle="Cypher">
|
||||||
|
<h5>This natural language is translated as the SQL:</h5>
|
||||||
|
<code>{cypher}</code>
|
||||||
|
</RegularCard>
|
||||||
|
}
|
||||||
|
</Grid>
|
||||||
|
<Grid item={true} xs={8} className={classes.leftPanel}>
|
||||||
|
<GraphPanel project={project}/>
|
||||||
|
</Grid>
|
||||||
|
<Grid item={true} xs={4} className={classes.rightPanel}>
|
||||||
|
<FindEntityPanel project={project}/>
|
||||||
|
<div className={classes.informationPanel}>
|
||||||
|
<InformationPanel/>
|
||||||
|
</div>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
const notShow = (
|
||||||
|
<Grid container={true} spacing={0}>
|
||||||
|
<LinearProgress/>
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
return fetchingGraph ? notShow : show;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withStyles(styles)<{ project: string }>(connect(mapStateToProps)(GraphPanels));
|
|
@ -0,0 +1,60 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import SearchForm from '../../../components/SearchForm';
|
||||||
|
import GraphPanels from './GraphPanels';
|
||||||
|
import { fetchGraphWorker } from '../../../redux/action';
|
||||||
|
import { GRAPH_PREDEFINED_QUERIES } from '../../../config';
|
||||||
|
import { RootState } from '../../../redux/reducer';
|
||||||
|
import { RouteComponentProps } from 'react-router';
|
||||||
|
import { LinearProgress, WithStyles } from 'material-ui';
|
||||||
|
import { container } from '../../../variables/styles';
|
||||||
|
import withStyles from 'material-ui/styles/withStyles';
|
||||||
|
|
||||||
|
const styles = () => ({
|
||||||
|
container: {
|
||||||
|
paddingTop: '60px',
|
||||||
|
...container
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapStateToProps = (state: RootState) => ({
|
||||||
|
query: state.graph.query,
|
||||||
|
fetching: state.graph.fetching
|
||||||
|
});
|
||||||
|
|
||||||
|
interface GraphTabRouteProps {
|
||||||
|
project: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type GraphTabStyles = WithStyles<'container'>;
|
||||||
|
|
||||||
|
interface GraphTabProps extends RouteComponentProps<GraphTabRouteProps> {
|
||||||
|
query: string;
|
||||||
|
fetching: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
class GraphTab extends React.Component<GraphTabProps & GraphTabStyles, {}> {
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {project} = this.props.match.params;
|
||||||
|
const {query, fetching, classes} = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<SearchForm
|
||||||
|
query={query}
|
||||||
|
predefinedQueries={GRAPH_PREDEFINED_QUERIES}
|
||||||
|
callback={(param: { query: string }) => fetchGraphWorker({project, query: param.query})}
|
||||||
|
/>
|
||||||
|
{fetching ?
|
||||||
|
<div className={classes.container}>
|
||||||
|
<LinearProgress/>
|
||||||
|
</div> :
|
||||||
|
<GraphPanels project={project}/>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withStyles(styles)<{}>(connect(mapStateToProps)(GraphTab));
|
|
@ -0,0 +1,100 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import {
|
||||||
|
LinearProgress, Table, TableBody, TableCell, TableRow, Typography, WithStyles
|
||||||
|
} from 'material-ui';
|
||||||
|
import withStyles from 'material-ui/styles/withStyles';
|
||||||
|
import { Option } from 'ts-option';
|
||||||
|
import * as _ from 'lodash';
|
||||||
|
import CodeModal from '../../../components/CodeModal';
|
||||||
|
import { RootState } from '../../../redux/reducer';
|
||||||
|
import { NodesState } from '../../../redux/graphReducer';
|
||||||
|
import RegularCard from '../../../components/Cards/RegularCard';
|
||||||
|
|
||||||
|
const mapStateToProps = (state: RootState) => {
|
||||||
|
return {
|
||||||
|
selectedNode: state.graph.selectedNode,
|
||||||
|
nodes: state.graph.nodes
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const styles = () => ({
|
||||||
|
normalCell: {
|
||||||
|
wordWrap: 'break-word',
|
||||||
|
whiteSpace: 'pre-wrap',
|
||||||
|
wordBreak: 'break-word',
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
interface InformationPanelProps {
|
||||||
|
selectedNode: Option<number>;
|
||||||
|
nodes: NodesState;
|
||||||
|
}
|
||||||
|
|
||||||
|
class InformationPanel extends React.Component<InformationPanelProps & WithStyles<'normalCell'>, {}> {
|
||||||
|
render() {
|
||||||
|
let body = null;
|
||||||
|
|
||||||
|
const {classes, selectedNode, nodes} = this.props;
|
||||||
|
|
||||||
|
if (selectedNode.isEmpty) {
|
||||||
|
body = <Typography component="p"> No entity selected. </Typography>;
|
||||||
|
} else {
|
||||||
|
const selected = nodes.get(selectedNode.get);
|
||||||
|
if (selected.nonEmpty) {
|
||||||
|
const node = selected.get.node;
|
||||||
|
let properties = Object.keys(node.properties)
|
||||||
|
.map(k => {
|
||||||
|
let content = node.properties[k];
|
||||||
|
if (content.length > 80) {
|
||||||
|
content = (
|
||||||
|
<CodeModal
|
||||||
|
code={k === 'content' || k === 'comment'}
|
||||||
|
label="SHOW"
|
||||||
|
content={content}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
content = <div className={classes.normalCell}>{content.toString()}</div>;
|
||||||
|
}
|
||||||
|
return {key: k, label: k, content};
|
||||||
|
});
|
||||||
|
properties = _.sortBy(properties, (entry) => {
|
||||||
|
if (entry.key === 'label') {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (entry.key.indexOf('name') !== -1) {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
if (entry.key.indexOf('signature') !== -1) {
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
if (entry.key.indexOf('title') !== -1) {
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
return 10;
|
||||||
|
});
|
||||||
|
body = (
|
||||||
|
<Table>
|
||||||
|
<TableBody>
|
||||||
|
{properties.map(p => <TableRow key={p.key}>
|
||||||
|
<TableCell>{p.label}</TableCell>
|
||||||
|
<TableCell>{p.content}</TableCell>
|
||||||
|
</TableRow>)}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
body = <LinearProgress/>;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RegularCard headerColor="blue" cardTitle="Read detailed properties">
|
||||||
|
{body}
|
||||||
|
</RegularCard>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withStyles(styles)<{}>(connect(mapStateToProps)(InformationPanel));
|
|
@ -0,0 +1,43 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import QueryTab from './GraphTab';
|
||||||
|
import Tabs, { Tab } from 'material-ui/Tabs';
|
||||||
|
import DocumentTab from './DocumentTab';
|
||||||
|
import DiagramTab from './DiagramTab';
|
||||||
|
import { Route, RouteComponentProps, Switch } from 'react-router';
|
||||||
|
|
||||||
|
interface ProjectPageRouteProps {
|
||||||
|
project: string;
|
||||||
|
tab: TabType;
|
||||||
|
}
|
||||||
|
|
||||||
|
type TabType = 'diagram' | 'graph' | 'document';
|
||||||
|
|
||||||
|
class ProjectPage extends React.Component<RouteComponentProps<ProjectPageRouteProps>> {
|
||||||
|
render() {
|
||||||
|
const {project, tab} = this.props.match.params;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Tabs
|
||||||
|
value={tab}
|
||||||
|
onChange={(e, v) => this.props.history.push(`/demo/${project}/${v}`)}
|
||||||
|
indicatorColor="primary"
|
||||||
|
textColor="primary"
|
||||||
|
scrollable={true}
|
||||||
|
scrollButtons="auto"
|
||||||
|
>
|
||||||
|
<Tab value="diagram" label="图谱概览"/>
|
||||||
|
<Tab value="graph" label="智能问答"/>
|
||||||
|
<Tab value="document" label="语义搜索"/>
|
||||||
|
</Tabs>
|
||||||
|
<Switch>
|
||||||
|
<Route path="/demo/:project/diagram" component={DiagramTab}/>
|
||||||
|
<Route path="/demo/:project/graph" component={QueryTab}/>
|
||||||
|
<Route path="/demo/:project/document" component={DocumentTab}/>
|
||||||
|
</Switch>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ProjectPage;
|
|
@ -0,0 +1,80 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import logo from '../assets/img/logo.png';
|
||||||
|
import { WithStyles, Typography } from 'material-ui';
|
||||||
|
import { withStyles } from 'material-ui/styles';
|
||||||
|
import { StyleRules } from 'material-ui/styles/withStyles';
|
||||||
|
import { container, flexContainer, primaryColor } from '../variables/styles';
|
||||||
|
import Carousel from '../components/Carousel/Carousel';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
type HomePageStyleKeys = 'container' | 'flexContainer' | 'logoTitle' | 'img' | 'swipe';
|
||||||
|
|
||||||
|
type HomePageStyle = WithStyles<HomePageStyleKeys>;
|
||||||
|
|
||||||
|
const styles = () => ({
|
||||||
|
container,
|
||||||
|
flexContainer,
|
||||||
|
swipe: {
|
||||||
|
width: '1000px',
|
||||||
|
},
|
||||||
|
img: {
|
||||||
|
maxWidth: 1000,
|
||||||
|
},
|
||||||
|
logoTitle: {
|
||||||
|
position: 'relative',
|
||||||
|
width: '300px',
|
||||||
|
left: '18px',
|
||||||
|
top: '18px'
|
||||||
|
}
|
||||||
|
}) as StyleRules<HomePageStyleKeys>;
|
||||||
|
|
||||||
|
const emph = (text: string) => (
|
||||||
|
<span style={{color: primaryColor}}>{text}</span>
|
||||||
|
);
|
||||||
|
|
||||||
|
const carousels = [
|
||||||
|
require('../assets/img/carousel.png'),
|
||||||
|
];
|
||||||
|
|
||||||
|
class HomePage extends React.Component<HomePageStyle, {}> {
|
||||||
|
render() {
|
||||||
|
const {classes} = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classes.container}>
|
||||||
|
<div className={classes.flexContainer}>
|
||||||
|
<h1 style={{display: 'inline'}}>
|
||||||
|
What is
|
||||||
|
<img className={classes.logoTitle} src={logo} alt="SnowGraph"/>
|
||||||
|
</h1>
|
||||||
|
<Typography component="h2" type="headline">
|
||||||
|
SnowGraph ({emph('S')}oftware K{emph('now')}ledge {emph('Graph')})
|
||||||
|
helps programmers learn how to reuse open source software
|
||||||
|
projects.
|
||||||
|
</Typography>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
It automatically extracts domain-specific knowledge graphs from the clutter of heterogeneous
|
||||||
|
software resources (e.g. source code, user tutorial, commit history, mailing lists, issue
|
||||||
|
reports and forum discussions).
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
It provides intelligent question answering services for programmers to query these software
|
||||||
|
knowledge graphs.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<h5>
|
||||||
|
SnowGraph for {<Link to="/demo/Lucene/diagram">Lucene</Link>}
|
||||||
|
</h5>
|
||||||
|
<div className={classes.swipe}>
|
||||||
|
<Carousel slideCount={carousels.length}>
|
||||||
|
{carousels.map(c => <img className={classes.img} src={c} alt={c} key={c} />)}
|
||||||
|
</Carousel>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withStyles(styles)<{}>(HomePage);
|
|
@ -0,0 +1,79 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import { WithStyles, Typography } from 'material-ui';
|
||||||
|
import { withStyles } from 'material-ui/styles';
|
||||||
|
import { StyleRules } from 'material-ui/styles/withStyles';
|
||||||
|
import RegularCard from '../components/Cards/RegularCard';
|
||||||
|
import { container } from '../variables/styles';
|
||||||
|
|
||||||
|
type ResourcePageStyleKeys = 'container';
|
||||||
|
|
||||||
|
type ResourcePageStyle = WithStyles<ResourcePageStyleKeys>;
|
||||||
|
|
||||||
|
const styles = () => ({
|
||||||
|
container: {
|
||||||
|
paddingTop: 40,
|
||||||
|
...container
|
||||||
|
}
|
||||||
|
}) as StyleRules<ResourcePageStyleKeys>;
|
||||||
|
|
||||||
|
const githubUrl = 'https://github.com/linzeqipku/intellide-graph';
|
||||||
|
|
||||||
|
const bibtex1 = `
|
||||||
|
@article{lin2017intelligent,
|
||||||
|
title={Intelligent Development Environment and Software Knowledge Graph},
|
||||||
|
author={Lin, Ze-Qi and Xie, Bing and Zou, Yan-Zhen and Zhao, Jun-Feng and Li, Xuan-Dong and Wei, Jun and Sun,\
|
||||||
|
Hai-Long and Yin, Gang},
|
||||||
|
journal={Journal of Computer Science and Technology},
|
||||||
|
volume={32},
|
||||||
|
number={2},
|
||||||
|
pages={242--249},
|
||||||
|
year={2017},
|
||||||
|
publisher={Springer}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const bibtex2 = `
|
||||||
|
@inproceedings{lin2017improving,
|
||||||
|
title={Improving software text retrieval using conceptual knowledge in source code},
|
||||||
|
author={Lin, Zeqi and Zou, Yanzhen and Zhao, Junfeng and Xie, Bing},
|
||||||
|
booktitle={Proceedings of the 32nd IEEE/ACM International Conference on Automated Software Engineering},
|
||||||
|
pages={123--134},
|
||||||
|
year={2017},
|
||||||
|
organization={IEEE Press}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
class ResourcePage extends React.Component<ResourcePageStyle, {}> {
|
||||||
|
render() {
|
||||||
|
const {classes} = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classes.container}>
|
||||||
|
<RegularCard cardTitle="Code" headerColor="red">
|
||||||
|
<a href={githubUrl}> {githubUrl} </a>
|
||||||
|
<Typography type="body1">
|
||||||
|
These programs and documents are distributed without any warranty, express or implied. As the programs were
|
||||||
|
written for research purposes only, they have not been tested to the degree that would be advisable in any
|
||||||
|
important application. All use of these programs is entirely at the user's own risk.
|
||||||
|
</Typography>
|
||||||
|
</RegularCard>
|
||||||
|
|
||||||
|
<RegularCard cardTitle="Documentation" headerColor="green">
|
||||||
|
<Typography type="body1">
|
||||||
|
will be uploaded in near future.
|
||||||
|
</Typography>
|
||||||
|
</RegularCard>
|
||||||
|
|
||||||
|
<RegularCard cardTitle="Papers" headerColor="orange">
|
||||||
|
<Typography type="body1">
|
||||||
|
If you use the program in your work, please cite:
|
||||||
|
</Typography>
|
||||||
|
<pre style={{wordWrap: 'break-word', whiteSpace: 'pre-wrap'}}>{bibtex1}</pre>
|
||||||
|
<pre style={{wordWrap: 'break-word', whiteSpace: 'pre-wrap'}}>{bibtex2}</pre>
|
||||||
|
</RegularCard>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withStyles(styles)<{}>(ResourcePage);
|
|
@ -0,0 +1,82 @@
|
||||||
|
import * as $ from 'jquery';
|
||||||
|
import actionCreatorFactory from 'typescript-fsa';
|
||||||
|
import bindThunkAction from 'typescript-fsa-redux-thunk';
|
||||||
|
import { CypherQueryResult, DocumentResult, Neo4jRelation } from '../model';
|
||||||
|
import { Neo4jNode } from '../model';
|
||||||
|
import { RootState } from './reducer';
|
||||||
|
import * as _ from 'lodash';
|
||||||
|
import { CODE_SEARCH_URL, DOCUMENT_SEARCH_URL, NODE_INFO_URL, RELATION_LIST_URL } from '../config';
|
||||||
|
|
||||||
|
const actionCreator = actionCreatorFactory();
|
||||||
|
|
||||||
|
export const fetchDocumentResult = actionCreator.async<{ project: string, query: string }, DocumentResult[]>(
|
||||||
|
'FETCH_DOCUMENT_RESULT'
|
||||||
|
);
|
||||||
|
export const fetchDocumentResultWorker = bindThunkAction(
|
||||||
|
fetchDocumentResult,
|
||||||
|
async (params) => {
|
||||||
|
return await $.post(DOCUMENT_SEARCH_URL, params);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const setCypher = actionCreator<string>('SET_CYPHER');
|
||||||
|
|
||||||
|
export const selectNode = actionCreator<number>('SELECT_NODE');
|
||||||
|
export const removeNode = actionCreator<number>('REMOVE_NODE');
|
||||||
|
export const addNodes = actionCreator<Neo4jNode[]>('ADD_NODES');
|
||||||
|
|
||||||
|
export const fetchNode = actionCreator.async<{ project: string, id: number }, Neo4jNode>('FETCH_NODE');
|
||||||
|
export const fetchNodeWorker = bindThunkAction(
|
||||||
|
fetchNode,
|
||||||
|
async (params, dispatch, getState: () => RootState) => {
|
||||||
|
const state = getState();
|
||||||
|
if (state.graph.nodes.has(params.id)) {
|
||||||
|
const node = state.graph.nodes.get(params.id);
|
||||||
|
if (node.nonEmpty) {
|
||||||
|
return node.get.node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return await $.post(NODE_INFO_URL, params);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const addShownRelations = actionCreator<Neo4jRelation[]>('ADD_SHOWN_RELATIONS');
|
||||||
|
export const addRelations = actionCreator<Neo4jRelation[]>('ADD_RELATIONS');
|
||||||
|
|
||||||
|
export const fetchRelationList = actionCreator.async<{ project: string, id: number }, string[]>('FETCH_RELATION_LIST');
|
||||||
|
export const fetchRelationListWorker = bindThunkAction(
|
||||||
|
fetchRelationList,
|
||||||
|
async (params, dispatch, getState: () => RootState) => {
|
||||||
|
const state = getState();
|
||||||
|
if (state.graph.relationLists.has(params.id)) {
|
||||||
|
const list = state.graph.relationLists.get(params.id);
|
||||||
|
if (list.nonEmpty) {
|
||||||
|
return list.get;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const result: Neo4jRelation[] = await $.post(RELATION_LIST_URL, params);
|
||||||
|
dispatch(addRelations(result));
|
||||||
|
return _.uniq(result.map(r => `${r.startNode},${r.endNode}`));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const showRelations = actionCreator<string[]>('SHOW_RELATIONS');
|
||||||
|
|
||||||
|
export const fetchGraph = actionCreator.async<{ project: string, query: string }, {}>('FETCH_GRAPH');
|
||||||
|
export const fetchGraphWorker = bindThunkAction(
|
||||||
|
fetchGraph,
|
||||||
|
async (params, dispatch) => {
|
||||||
|
const result: CypherQueryResult = await $.post(CODE_SEARCH_URL, params);
|
||||||
|
const cypher = result.cypher;
|
||||||
|
const nodes = result.nodes;
|
||||||
|
const relations = result.relationships;
|
||||||
|
|
||||||
|
if (cypher && cypher.length > 0) {
|
||||||
|
dispatch(setCypher(cypher));
|
||||||
|
} else {
|
||||||
|
dispatch(setCypher(''));
|
||||||
|
}
|
||||||
|
dispatch(addNodes(nodes));
|
||||||
|
dispatch(addShownRelations(relations));
|
||||||
|
return {};
|
||||||
|
});
|
|
@ -0,0 +1,104 @@
|
||||||
|
import { reducerWithInitialState } from 'typescript-fsa-reducers';
|
||||||
|
import {
|
||||||
|
addNodes, addRelations, addShownRelations,
|
||||||
|
fetchGraph, fetchNode, fetchRelationList,
|
||||||
|
removeNode,
|
||||||
|
selectNode, setCypher, showRelations
|
||||||
|
} from './action';
|
||||||
|
import { combineReducers } from 'redux';
|
||||||
|
import { Neo4jRelation, SnowNode, SnowRelation } from '../model';
|
||||||
|
import { Map } from 'immutable';
|
||||||
|
import { none, Option, some } from 'ts-option';
|
||||||
|
import { withError } from '../utils/utils';
|
||||||
|
import * as _ from 'lodash';
|
||||||
|
|
||||||
|
export type NodesState = Map<number, Option<SnowNode>>;
|
||||||
|
|
||||||
|
export type RelationsState = Map<string, SnowRelation>;
|
||||||
|
|
||||||
|
export type RelationListsState = Map<number, Option<string[]>>;
|
||||||
|
|
||||||
|
export interface GraphState {
|
||||||
|
fetching: boolean;
|
||||||
|
selectedNode: Option<number>;
|
||||||
|
query: string;
|
||||||
|
cypher: string;
|
||||||
|
nodes: NodesState;
|
||||||
|
relations: RelationsState;
|
||||||
|
relationLists: RelationListsState;
|
||||||
|
}
|
||||||
|
|
||||||
|
function r2id(neo4jRelation: Neo4jRelation) {
|
||||||
|
return `${neo4jRelation.startNode},${neo4jRelation.endNode}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetching = reducerWithInitialState<boolean>(false)
|
||||||
|
.case(fetchGraph.started, () => true)
|
||||||
|
.case(fetchGraph.done, () => false)
|
||||||
|
.case(fetchGraph.failed, () => withError('Failed to execute CypherQuery', false));
|
||||||
|
|
||||||
|
const selectedNode = reducerWithInitialState<Option<number>>(none)
|
||||||
|
.case(fetchGraph.started, () => none)
|
||||||
|
.case(selectNode, (s, p) => some(p))
|
||||||
|
.case(removeNode, (s, p) => s.exists(num => num === p) ? none : s);
|
||||||
|
|
||||||
|
const nodes = reducerWithInitialState<NodesState>(Map())
|
||||||
|
.case(fetchGraph.started, () => Map())
|
||||||
|
.case(fetchNode.started, (s, p) => s.update(p.id, (val = none) => val))
|
||||||
|
.case(fetchNode.done, (s, p) => s.set(p.params.id, some(new SnowNode(true, p.result))))
|
||||||
|
.case(
|
||||||
|
fetchNode.failed,
|
||||||
|
(s, p) => withError('Failed to get node', s.get(p.params.id).isEmpty ? s.remove(p.params.id) : s)
|
||||||
|
)
|
||||||
|
.case(addNodes, (s, p) => p.reduce((prev, n) => prev.set(n.id, some(new SnowNode(true, n))), s))
|
||||||
|
.case(removeNode, (s, p) => s.set(p, s.get(p).map(sn => new SnowNode(false, sn.node))));
|
||||||
|
|
||||||
|
const relations = reducerWithInitialState<RelationsState>(Map())
|
||||||
|
.case(fetchGraph.started, () => Map())
|
||||||
|
.case(addShownRelations, (s, p) => {
|
||||||
|
const grouped = _.groupBy(p, r2id);
|
||||||
|
let newState = s;
|
||||||
|
for (const id of Object.keys(grouped)) {
|
||||||
|
const [source, target] = id.split(',').map(x => parseInt(x, 10));
|
||||||
|
const types = _.uniq(grouped[id].map(r => r.type));
|
||||||
|
newState = newState.set(id, {shown: true, source, target, id, types});
|
||||||
|
}
|
||||||
|
return newState;
|
||||||
|
})
|
||||||
|
.case(addRelations, (s, p) => {
|
||||||
|
const grouped = _.groupBy(p, r2id);
|
||||||
|
let newState = s;
|
||||||
|
for (const id of Object.keys(grouped)) {
|
||||||
|
const [source, target] = id.split(',').map(x => parseInt(x, 10));
|
||||||
|
const types = _.uniq(grouped[id].map(r => r.type));
|
||||||
|
if (newState.has(id)) {
|
||||||
|
newState = newState.update(id, r => Object.assign({}, r, {types: _.uniq([...r.types, ...types])}));
|
||||||
|
} else {
|
||||||
|
newState = newState.set(id, {shown: false, source, target, id, types});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newState;
|
||||||
|
})
|
||||||
|
.case(showRelations, (s, p) =>
|
||||||
|
p.reduce((prev, r) => prev.update(r, old => Object.assign({}, old, {shown: true})), s))
|
||||||
|
.case(removeNode, (s, p) => (s.map(value => value!.source === p || value!.target === p ?
|
||||||
|
Object.assign({}, value, {shown: false}) : value!) as Map<string, SnowRelation>
|
||||||
|
));
|
||||||
|
|
||||||
|
const relationLists = reducerWithInitialState<RelationListsState>(Map())
|
||||||
|
.case(fetchGraph.started, () => Map())
|
||||||
|
.case(fetchRelationList.started, (s, p) => s.set(p.id, s.get(p.id, none)))
|
||||||
|
.case(fetchRelationList.done, (s, p) => s.set(p.params.id, some(p.result)))
|
||||||
|
.case(fetchRelationList.failed, (s, p) =>
|
||||||
|
withError('Failed to get relation list', s.remove(p.params.id))
|
||||||
|
);
|
||||||
|
|
||||||
|
const query = reducerWithInitialState<string>('')
|
||||||
|
.case(fetchGraph.started, (s, p) => p.query);
|
||||||
|
|
||||||
|
const cypher = reducerWithInitialState<string>('')
|
||||||
|
.case(setCypher, (state, payload) => payload);
|
||||||
|
|
||||||
|
export const graph = combineReducers({
|
||||||
|
fetching, selectedNode, query, cypher, nodes, relations, relationLists
|
||||||
|
});
|
|
@ -0,0 +1,51 @@
|
||||||
|
import { reducerWithInitialState } from 'typescript-fsa-reducers';
|
||||||
|
import {
|
||||||
|
fetchDocumentResult
|
||||||
|
} from './action';
|
||||||
|
import { combineReducers } from 'redux';
|
||||||
|
import { DocumentResult } from '../model';
|
||||||
|
import { show } from 'js-snackbar';
|
||||||
|
import { graph, GraphState } from './graphReducer';
|
||||||
|
|
||||||
|
function showError(message: string) {
|
||||||
|
show({
|
||||||
|
text: message,
|
||||||
|
pos: 'bottom-center',
|
||||||
|
duration: 1000
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function withError<V>(message: string, value: V): V {
|
||||||
|
showError(message);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DocumentResultState {
|
||||||
|
fetching: boolean;
|
||||||
|
query: string;
|
||||||
|
result?: DocumentResult[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RootState {
|
||||||
|
fetchingRandomQuestion: boolean;
|
||||||
|
graph: GraphState;
|
||||||
|
documentResult: DocumentResultState;
|
||||||
|
}
|
||||||
|
|
||||||
|
const documentResult =
|
||||||
|
reducerWithInitialState<DocumentResultState>({fetching: false, query: ''})
|
||||||
|
.case(fetchDocumentResult.started, (state, payload) => ({fetching: true, query: payload.query}))
|
||||||
|
.case(fetchDocumentResult.done, (state, payload) => {
|
||||||
|
for (let i = 0; i < payload.result.length; ++i) {
|
||||||
|
payload.result[i].rank = i + 1;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
fetching: false, query: payload.params.query, result: payload.result
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.case(fetchDocumentResult.failed, (state, payload) =>
|
||||||
|
withError('Failed to rank', {fetching: false, query: payload.params.query}));
|
||||||
|
|
||||||
|
export const appReducer = combineReducers({
|
||||||
|
graph, documentResult
|
||||||
|
});
|
|
@ -0,0 +1,114 @@
|
||||||
|
// tslint:disable:no-console
|
||||||
|
// In production, we register a service worker to serve assets from local cache.
|
||||||
|
|
||||||
|
// This lets the app load faster on subsequent visits in production, and gives
|
||||||
|
// it offline capabilities. However, it also means that developers (and users)
|
||||||
|
// will only see deployed updates on the 'N+1' visit to a page, since previously
|
||||||
|
// cached resources are updated in the background.
|
||||||
|
|
||||||
|
// To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
|
||||||
|
// This link also includes instructions on opting out of this behavior.
|
||||||
|
|
||||||
|
const isLocalhost = Boolean(
|
||||||
|
window.location.hostname === 'localhost' ||
|
||||||
|
// [::1] is the IPv6 localhost address.
|
||||||
|
window.location.hostname === '[::1]' ||
|
||||||
|
// 127.0.0.1/8 is considered localhost for IPv4.
|
||||||
|
window.location.hostname.match(
|
||||||
|
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
export default function register() {
|
||||||
|
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
|
||||||
|
// The URL constructor is available in all browsers that support SW.
|
||||||
|
const publicUrl = new URL(
|
||||||
|
process.env.PUBLIC_URL!,
|
||||||
|
window.location.toString()
|
||||||
|
);
|
||||||
|
if (publicUrl.origin !== window.location.origin) {
|
||||||
|
// Our service worker won't work if PUBLIC_URL is on a different origin
|
||||||
|
// from what our page is served on. This might happen if a CDN is used to
|
||||||
|
// serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('load', () => {
|
||||||
|
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
|
||||||
|
|
||||||
|
if (!isLocalhost) {
|
||||||
|
// Is not local host. Just register service worker
|
||||||
|
registerValidSW(swUrl);
|
||||||
|
} else {
|
||||||
|
// This is running on localhost. Lets check if a service worker still exists or not.
|
||||||
|
checkValidServiceWorker(swUrl);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function registerValidSW(swUrl: string) {
|
||||||
|
navigator.serviceWorker
|
||||||
|
.register(swUrl)
|
||||||
|
.then(registration => {
|
||||||
|
registration.onupdatefound = () => {
|
||||||
|
const installingWorker = registration.installing;
|
||||||
|
if (installingWorker) {
|
||||||
|
installingWorker.onstatechange = () => {
|
||||||
|
if (installingWorker.state === 'installed') {
|
||||||
|
if (navigator.serviceWorker.controller) {
|
||||||
|
// At this point, the old content will have been purged and
|
||||||
|
// the fresh content will have been added to the cache.
|
||||||
|
// It's the perfect time to display a 'New content is
|
||||||
|
// available; please refresh.' message in your web app.
|
||||||
|
console.log('New content is available; please refresh.');
|
||||||
|
} else {
|
||||||
|
// At this point, everything has been precached.
|
||||||
|
// It's the perfect time to display a
|
||||||
|
// 'Content is cached for offline use.' message.
|
||||||
|
console.log('Content is cached for offline use.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error during service worker registration:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkValidServiceWorker(swUrl: string) {
|
||||||
|
// Check if the service worker can be found. If it can't reload the page.
|
||||||
|
fetch(swUrl)
|
||||||
|
.then(response => {
|
||||||
|
// Ensure service worker exists, and that we really are getting a JS file.
|
||||||
|
if (
|
||||||
|
response.status === 404 ||
|
||||||
|
response.headers.get('content-type')!.indexOf('javascript') === -1
|
||||||
|
) {
|
||||||
|
// No service worker found. Probably a different app. Reload the page.
|
||||||
|
navigator.serviceWorker.ready.then(registration => {
|
||||||
|
registration.unregister().then(() => {
|
||||||
|
window.location.reload();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Service worker found. Proceed as normal.
|
||||||
|
registerValidSW(swUrl);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
console.log(
|
||||||
|
'No internet connection found. App is running in offline mode.'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function unregister() {
|
||||||
|
if ('serviceWorker' in navigator) {
|
||||||
|
navigator.serviceWorker.ready.then(registration => {
|
||||||
|
registration.unregister();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
import {
|
||||||
|
Home,
|
||||||
|
Dashboard,
|
||||||
|
Code,
|
||||||
|
Person,
|
||||||
|
// ContentPaste,
|
||||||
|
// LibraryBooks,
|
||||||
|
// BubbleChart,
|
||||||
|
// LocationOn,
|
||||||
|
// Notifications
|
||||||
|
} from 'material-ui-icons';
|
||||||
|
import { SvgIconProps } from 'material-ui/SvgIcon';
|
||||||
|
import HomePage from '../pages/HomePage';
|
||||||
|
import DemoPage from '../pages/DemoPage/DemoPage';
|
||||||
|
import ResourcePage from '../pages/ResourcePage';
|
||||||
|
import * as React from 'react';
|
||||||
|
import { RouteComponentProps } from 'react-router';
|
||||||
|
import AboutPage from '../pages/AboutPage';
|
||||||
|
|
||||||
|
export interface AppRoute {
|
||||||
|
path: string;
|
||||||
|
sidebarName: string;
|
||||||
|
navbarName: string;
|
||||||
|
icon: React.ComponentType<SvgIconProps>;
|
||||||
|
exact: boolean;
|
||||||
|
component: React.ComponentType<RouteComponentProps<{}>> | React.ComponentType<{}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const appRoutes: AppRoute[] = [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
sidebarName: 'Intro',
|
||||||
|
navbarName: 'Intro',
|
||||||
|
icon: Home,
|
||||||
|
exact: true,
|
||||||
|
component: HomePage
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/demo',
|
||||||
|
sidebarName: 'Use It',
|
||||||
|
navbarName: 'Use It',
|
||||||
|
icon: Dashboard,
|
||||||
|
exact: false,
|
||||||
|
component: DemoPage
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/resource',
|
||||||
|
sidebarName: 'Resource',
|
||||||
|
navbarName: 'Resource',
|
||||||
|
icon: Code,
|
||||||
|
exact: true,
|
||||||
|
component: ResourcePage
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/about',
|
||||||
|
sidebarName: 'About',
|
||||||
|
navbarName: 'About',
|
||||||
|
icon: Person,
|
||||||
|
exact: true,
|
||||||
|
component: AboutPage
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
export default appRoutes;
|
|
@ -0,0 +1,5 @@
|
||||||
|
import App from '../App';
|
||||||
|
|
||||||
|
const indexRoutes = [{ path: '/', component: App }];
|
||||||
|
|
||||||
|
export default indexRoutes;
|
|
@ -0,0 +1,70 @@
|
||||||
|
import { show } from 'js-snackbar';
|
||||||
|
import { hsl } from 'd3-color';
|
||||||
|
|
||||||
|
export function showError(message: string) {
|
||||||
|
show({
|
||||||
|
text: message,
|
||||||
|
pos: 'bottom-center',
|
||||||
|
duration: 1000
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function withError<V>(message: string, value: V): V {
|
||||||
|
showError(message);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const I64BIT_TABLE = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-'.split('');
|
||||||
|
|
||||||
|
function hash0(input: string): string {
|
||||||
|
let hash = 5381;
|
||||||
|
let i = input.length - 1;
|
||||||
|
|
||||||
|
for (; i > -1; i--) {
|
||||||
|
hash += (hash << 5) + input.charCodeAt(i);
|
||||||
|
}
|
||||||
|
let value = hash & 0x7FFFFFFF;
|
||||||
|
|
||||||
|
let retValue = '';
|
||||||
|
do {
|
||||||
|
retValue += I64BIT_TABLE[value & 0x3F];
|
||||||
|
}
|
||||||
|
while (value >>= 6);
|
||||||
|
|
||||||
|
return retValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function colorHash(s: string): number {
|
||||||
|
s = hash0(s);
|
||||||
|
let h = 0;
|
||||||
|
for (let i = 0; i < s.length; ++i) {
|
||||||
|
const chr = s.charCodeAt(i);
|
||||||
|
h = h * 79 + chr;
|
||||||
|
}
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function name2color(name: string): string {
|
||||||
|
if (name === 'Class') {
|
||||||
|
return '#43CD80';
|
||||||
|
}
|
||||||
|
if (name === 'Method') {
|
||||||
|
return '#9AFF9A';
|
||||||
|
}
|
||||||
|
if (name === 'Field') {
|
||||||
|
return '#C1FFC1';
|
||||||
|
}
|
||||||
|
if (name === 'GitUser') {
|
||||||
|
return '#D3D3D3';
|
||||||
|
}
|
||||||
|
if (name === 'Commit') {
|
||||||
|
return '#708090';
|
||||||
|
}
|
||||||
|
if (name === 'Docx') {
|
||||||
|
return '#4169E1';
|
||||||
|
}
|
||||||
|
const h = colorHash(name) % 360;
|
||||||
|
return Math.ceil(h / 30) % 2 === 0 ?
|
||||||
|
hsl(h, 1, 0.8).rgb().toString() :
|
||||||
|
hsl(h, 1, 0.6).rgb().toString();
|
||||||
|
}
|
|
@ -0,0 +1,162 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
const drawerWidth = 200;
|
||||||
|
|
||||||
|
const footerHeight = 80;
|
||||||
|
|
||||||
|
const transition = {
|
||||||
|
transition: 'all 0.33s cubic-bezier(0.685, 0.0473, 0.346, 1)'
|
||||||
|
};
|
||||||
|
|
||||||
|
const container = {
|
||||||
|
paddingRight: '60px',
|
||||||
|
paddingLeft: '60px',
|
||||||
|
};
|
||||||
|
|
||||||
|
const flexContainer = {
|
||||||
|
alignItems: 'center',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
};
|
||||||
|
|
||||||
|
const content = {
|
||||||
|
marginTop: '30px',
|
||||||
|
padding: '30px 15px',
|
||||||
|
};
|
||||||
|
|
||||||
|
const boxShadow = {
|
||||||
|
boxShadow:
|
||||||
|
'0 10px 30px -12px rgba(0, 0, 0, 0.42), 0 4px 25px 0px rgba(0, 0, 0, 0.12), 0 8px 10px -5px rgba(0, 0, 0, 0.2)'
|
||||||
|
};
|
||||||
|
|
||||||
|
const card = {
|
||||||
|
display: 'inline-block',
|
||||||
|
position: 'relative',
|
||||||
|
width: '100%',
|
||||||
|
margin: '25px 0',
|
||||||
|
boxShadow: '0 1px 4px 0 rgba(0, 0, 0, 0.14)',
|
||||||
|
borderRadius: '3px',
|
||||||
|
color: 'rgba(0, 0, 0, 0.87)',
|
||||||
|
background: '#fff'
|
||||||
|
} as React.CSSProperties;
|
||||||
|
|
||||||
|
const defaultFont = {
|
||||||
|
fontFamily: '"Roboto" , "Noto Sans CJK SC", "Helvetica", "Arial", sans-serif',
|
||||||
|
fontWeight: 300,
|
||||||
|
lineHeight: '1.5em'
|
||||||
|
} as React.CSSProperties;
|
||||||
|
|
||||||
|
const primaryColor = '#9c27b0';
|
||||||
|
const warningColor = '#ff9800';
|
||||||
|
const dangerColor = '#f44336';
|
||||||
|
const successColor = '#4caf50';
|
||||||
|
const infoColor = '#00acc1';
|
||||||
|
const roseColor = '#e91e63';
|
||||||
|
const grayColor = '#999999';
|
||||||
|
|
||||||
|
const primaryBoxShadow = {
|
||||||
|
boxShadow:
|
||||||
|
`0 12px 20px -10px rgba(156, 39, 176, 0.28),\
|
||||||
|
0 4px 20px 0px rgba(0, 0, 0, 0.12),\
|
||||||
|
0 7px 8px -5px rgba(156, 39, 176, 0.2)`
|
||||||
|
};
|
||||||
|
const infoBoxShadow = {
|
||||||
|
boxShadow:
|
||||||
|
`0 12px 20px -10px rgba(0, 188, 212, 0.28),\
|
||||||
|
0 4px 20px 0px rgba(0, 0, 0, 0.12),\
|
||||||
|
0 7px 8px -5px rgba(0, 188, 212, 0.2)`
|
||||||
|
};
|
||||||
|
const successBoxShadow = {
|
||||||
|
boxShadow:
|
||||||
|
`0 12px 20px -10px rgba(76, 175, 80, 0.28),\
|
||||||
|
0 4px 20px 0px rgba(0, 0, 0, 0.12),\
|
||||||
|
0 7px 8px -5px rgba(76, 175, 80, 0.2)`
|
||||||
|
};
|
||||||
|
const warningBoxShadow = {
|
||||||
|
boxShadow:
|
||||||
|
`0 12px 20px -10px rgba(255, 152, 0, 0.28),\
|
||||||
|
0 4px 20px 0px rgba(0, 0, 0, 0.12),\
|
||||||
|
0 7px 8px -5px rgba(255, 152, 0, 0.2)`
|
||||||
|
};
|
||||||
|
const dangerBoxShadow = {
|
||||||
|
boxShadow:
|
||||||
|
`0 12px 20px -10px rgba(244, 67, 54, 0.28),\
|
||||||
|
0 4px 20px 0px rgba(0, 0, 0, 0.12),\
|
||||||
|
0 7px 8px -5px rgba(244, 67, 54, 0.2)`
|
||||||
|
};
|
||||||
|
|
||||||
|
const orangeCardHeader = {
|
||||||
|
background: 'linear-gradient(60deg, #ffa726, #fb8c00)',
|
||||||
|
...warningBoxShadow
|
||||||
|
};
|
||||||
|
const greenCardHeader = {
|
||||||
|
background: 'linear-gradient(60deg, #66bb6a, #43a047)',
|
||||||
|
...successBoxShadow
|
||||||
|
};
|
||||||
|
const redCardHeader = {
|
||||||
|
background: 'linear-gradient(60deg, #ef5350, #e53935)',
|
||||||
|
...dangerBoxShadow
|
||||||
|
};
|
||||||
|
const blueCardHeader = {
|
||||||
|
background: 'linear-gradient(60deg, #26c6da, #00acc1)',
|
||||||
|
...infoBoxShadow
|
||||||
|
};
|
||||||
|
const purpleCardHeader = {
|
||||||
|
background: 'linear-gradient(60deg, #ab47bc, #8e24aa)',
|
||||||
|
...primaryBoxShadow
|
||||||
|
};
|
||||||
|
|
||||||
|
const cardActions = {
|
||||||
|
margin: '0 20px 10px',
|
||||||
|
paddingTop: '10px',
|
||||||
|
borderTop: '1px solid #eeeeee',
|
||||||
|
height: 'auto',
|
||||||
|
...defaultFont
|
||||||
|
};
|
||||||
|
|
||||||
|
const cardHeader = {
|
||||||
|
margin: '-20px 15px 0',
|
||||||
|
borderRadius: '3px',
|
||||||
|
padding: '15px'
|
||||||
|
};
|
||||||
|
|
||||||
|
const defaultBoxShadow = {
|
||||||
|
border: '0',
|
||||||
|
borderRadius: '3px',
|
||||||
|
boxShadow:
|
||||||
|
'0 10px 20px -12px rgba(0, 0, 0, 0.42), 0 3px 20px 0px rgba(0, 0, 0, 0.12), 0 8px 10px -5px rgba(0, 0, 0, 0.2)',
|
||||||
|
padding: '10px 0',
|
||||||
|
transition: 'all 150ms ease 0s'
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
drawerWidth,
|
||||||
|
footerHeight,
|
||||||
|
transition,
|
||||||
|
container,
|
||||||
|
flexContainer,
|
||||||
|
content,
|
||||||
|
boxShadow,
|
||||||
|
card,
|
||||||
|
defaultFont,
|
||||||
|
primaryColor,
|
||||||
|
warningColor,
|
||||||
|
dangerColor,
|
||||||
|
successColor,
|
||||||
|
infoColor,
|
||||||
|
roseColor,
|
||||||
|
grayColor,
|
||||||
|
primaryBoxShadow,
|
||||||
|
infoBoxShadow,
|
||||||
|
successBoxShadow,
|
||||||
|
warningBoxShadow,
|
||||||
|
dangerBoxShadow,
|
||||||
|
orangeCardHeader,
|
||||||
|
greenCardHeader,
|
||||||
|
redCardHeader,
|
||||||
|
blueCardHeader,
|
||||||
|
purpleCardHeader,
|
||||||
|
cardActions,
|
||||||
|
cardHeader,
|
||||||
|
defaultBoxShadow
|
||||||
|
};
|
|
@ -0,0 +1,24 @@
|
||||||
|
import { WithStyles } from 'material-ui';
|
||||||
|
import { drawerWidth, footerHeight, transition } from '../styles';
|
||||||
|
import { StyleRules } from 'material-ui/styles';
|
||||||
|
|
||||||
|
type AppStyleKeys = 'mainPanel' | 'content';
|
||||||
|
|
||||||
|
export type AppStyle = WithStyles<AppStyleKeys>;
|
||||||
|
|
||||||
|
const appStyle = () => ({
|
||||||
|
mainPanel: {
|
||||||
|
width: `calc(100% - ${drawerWidth}px)`,
|
||||||
|
overflow: 'auto',
|
||||||
|
position: 'relative',
|
||||||
|
float: 'right',
|
||||||
|
...transition,
|
||||||
|
maxHeight: '100%',
|
||||||
|
overflowScrolling: 'touch'
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
minHeight: `calc(100vh - ${footerHeight}px)`
|
||||||
|
},
|
||||||
|
}) as StyleRules<AppStyleKeys>;
|
||||||
|
|
||||||
|
export default appStyle;
|
|
@ -0,0 +1,25 @@
|
||||||
|
import { defaultFont, container } from '../styles';
|
||||||
|
import { WithStyles } from 'material-ui';
|
||||||
|
import { StyleRules } from 'material-ui/styles';
|
||||||
|
|
||||||
|
type FooterStyleKeys = 'right' | 'footer' | 'container';
|
||||||
|
|
||||||
|
export type FooterStyle = WithStyles<FooterStyleKeys>;
|
||||||
|
|
||||||
|
const footerStyle = {
|
||||||
|
right: {
|
||||||
|
padding: '15px 0',
|
||||||
|
margin: '0',
|
||||||
|
fontSize: '14px',
|
||||||
|
float: 'right!important'
|
||||||
|
},
|
||||||
|
footer: {
|
||||||
|
bottom: '0',
|
||||||
|
borderTop: '1px solid #e7e7e7',
|
||||||
|
padding: '15px 0',
|
||||||
|
...defaultFont
|
||||||
|
},
|
||||||
|
container,
|
||||||
|
} as StyleRules<FooterStyleKeys>;
|
||||||
|
|
||||||
|
export default footerStyle;
|
|
@ -0,0 +1,45 @@
|
||||||
|
import { WithStyles } from 'material-ui';
|
||||||
|
import { defaultFont, infoColor } from '../styles';
|
||||||
|
import { StyleRules, Theme } from 'material-ui/styles';
|
||||||
|
|
||||||
|
type MatTableStyleKeys = 'tableHeader' | 'table' | 'tableHeadCell' | 'tableRow' | 'tableCell' | 'tableResponsive';
|
||||||
|
|
||||||
|
export type MatTableStyle = WithStyles<MatTableStyleKeys>;
|
||||||
|
|
||||||
|
const matTableStyle = (theme: Theme) => ({
|
||||||
|
tableHeader: {
|
||||||
|
color: infoColor
|
||||||
|
},
|
||||||
|
table: {
|
||||||
|
marginBottom: '0',
|
||||||
|
width: '100%',
|
||||||
|
maxWidth: '100%',
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
borderSpacing: '0',
|
||||||
|
borderCollapse: 'collapse'
|
||||||
|
},
|
||||||
|
tableHeadCell: {
|
||||||
|
color: 'inherit',
|
||||||
|
...defaultFont,
|
||||||
|
fontSize: '1.2em',
|
||||||
|
fontWeight: 400
|
||||||
|
},
|
||||||
|
tableRow: {
|
||||||
|
'&:last-child > td': {
|
||||||
|
borderBottom: '0'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tableCell: {
|
||||||
|
...defaultFont,
|
||||||
|
lineHeight: '1.42857143',
|
||||||
|
padding: '12px 8px',
|
||||||
|
verticalAlign: 'middle'
|
||||||
|
},
|
||||||
|
tableResponsive: {
|
||||||
|
width: '100%',
|
||||||
|
marginTop: theme.spacing.unit,
|
||||||
|
overflowX: 'auto'
|
||||||
|
}
|
||||||
|
}) as StyleRules<MatTableStyleKeys>;
|
||||||
|
|
||||||
|
export default matTableStyle;
|
|
@ -0,0 +1,64 @@
|
||||||
|
import { WithStyles } from 'material-ui';
|
||||||
|
import {
|
||||||
|
blueCardHeader, card, cardHeader, defaultFont, greenCardHeader, orangeCardHeader, purpleCardHeader,
|
||||||
|
redCardHeader
|
||||||
|
} from '../styles';
|
||||||
|
import { StyleRules } from 'material-ui/styles';
|
||||||
|
|
||||||
|
type RegularCardStyleKeys = 'card' |
|
||||||
|
'cardPlain' |
|
||||||
|
'cardHeader' |
|
||||||
|
'cardPlainHeader' |
|
||||||
|
'orangeCardHeader' |
|
||||||
|
'greenCardHeader' |
|
||||||
|
'redCardHeader' |
|
||||||
|
'blueCardHeader' |
|
||||||
|
'purpleCardHeader' |
|
||||||
|
'cardTitle' |
|
||||||
|
'cardSubtitle' |
|
||||||
|
'cardActions';
|
||||||
|
|
||||||
|
export type RegularCardStyle = WithStyles<RegularCardStyleKeys>;
|
||||||
|
|
||||||
|
const regularCardStyle = () => ({
|
||||||
|
card,
|
||||||
|
cardPlain: {
|
||||||
|
background: 'transparent',
|
||||||
|
boxShadow: 'none'
|
||||||
|
},
|
||||||
|
cardHeader: {
|
||||||
|
// display: 'inline-block',
|
||||||
|
// width: '200px',
|
||||||
|
...cardHeader,
|
||||||
|
...defaultFont
|
||||||
|
},
|
||||||
|
cardPlainHeader: {
|
||||||
|
marginLeft: 0,
|
||||||
|
marginRight: 0
|
||||||
|
},
|
||||||
|
orangeCardHeader,
|
||||||
|
greenCardHeader,
|
||||||
|
redCardHeader,
|
||||||
|
blueCardHeader,
|
||||||
|
purpleCardHeader,
|
||||||
|
cardTitle: {
|
||||||
|
color: '#FFFFFF',
|
||||||
|
marginTop: '0',
|
||||||
|
marginBottom: '5px',
|
||||||
|
...defaultFont,
|
||||||
|
fontSize: '1.125em'
|
||||||
|
},
|
||||||
|
cardSubtitle: {
|
||||||
|
...defaultFont,
|
||||||
|
marginBottom: '0',
|
||||||
|
color: 'rgba(255, 255, 255, 0.62)',
|
||||||
|
margin: '0 0 10px'
|
||||||
|
},
|
||||||
|
cardActions: {
|
||||||
|
padding: '14px',
|
||||||
|
display: 'block',
|
||||||
|
height: 'auto'
|
||||||
|
}
|
||||||
|
}) as StyleRules<RegularCardStyleKeys>;
|
||||||
|
|
||||||
|
export default regularCardStyle;
|
|
@ -0,0 +1,166 @@
|
||||||
|
import { WithStyles } from 'material-ui';
|
||||||
|
import { boxShadow, defaultFont, drawerWidth, infoColor } from '../styles';
|
||||||
|
import { StyleRules, Theme } from 'material-ui/styles';
|
||||||
|
|
||||||
|
type SidebarStyleKeys = 'drawerPaper' |
|
||||||
|
'logo' |
|
||||||
|
'logoLink' |
|
||||||
|
'logoImage' |
|
||||||
|
'img' |
|
||||||
|
'background' |
|
||||||
|
'list' |
|
||||||
|
'item' |
|
||||||
|
'itemLink' |
|
||||||
|
'itemIcon' |
|
||||||
|
'itemText' |
|
||||||
|
'whiteFont' |
|
||||||
|
'blue' |
|
||||||
|
'sidebarWrapper';
|
||||||
|
|
||||||
|
export type SidebarStyle = WithStyles<SidebarStyleKeys>;
|
||||||
|
|
||||||
|
const sidebarStyle = (theme: Theme) => ({
|
||||||
|
drawerPaper: {
|
||||||
|
border: 'none',
|
||||||
|
position: 'fixed',
|
||||||
|
top: '0',
|
||||||
|
bottom: '0',
|
||||||
|
left: '0',
|
||||||
|
zIndex: 1,
|
||||||
|
...boxShadow,
|
||||||
|
width: drawerWidth,
|
||||||
|
height: '100%',
|
||||||
|
},
|
||||||
|
logo: {
|
||||||
|
position: 'relative',
|
||||||
|
padding: '15px 15px',
|
||||||
|
zIndex: 4,
|
||||||
|
'&:after': {
|
||||||
|
content: '""',
|
||||||
|
position: 'absolute',
|
||||||
|
bottom: '0',
|
||||||
|
|
||||||
|
height: '1px',
|
||||||
|
right: '15px',
|
||||||
|
width: 'calc(100% - 30px)',
|
||||||
|
backgroundColor: 'rgba(180, 180, 180, 0.3)'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
logoLink: {
|
||||||
|
...defaultFont,
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
padding: '5px 0',
|
||||||
|
display: 'block',
|
||||||
|
fontSize: '18px',
|
||||||
|
textAlign: 'left',
|
||||||
|
fontWeight: 400,
|
||||||
|
lineHeight: '30px',
|
||||||
|
textDecoration: 'none',
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
'&,&:hover': {
|
||||||
|
color: '#FFFFFF'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
logoImage: {
|
||||||
|
width: '30px',
|
||||||
|
display: 'inline-block',
|
||||||
|
maxHeight: '30px',
|
||||||
|
marginLeft: '10px',
|
||||||
|
marginRight: '15px'
|
||||||
|
},
|
||||||
|
img: {
|
||||||
|
width: '140px',
|
||||||
|
top: '22px',
|
||||||
|
position: 'absolute',
|
||||||
|
verticalAlign: 'middle',
|
||||||
|
border: '0'
|
||||||
|
},
|
||||||
|
background: {
|
||||||
|
position: 'absolute',
|
||||||
|
zIndex: 1,
|
||||||
|
height: '100%',
|
||||||
|
width: '100%',
|
||||||
|
display: 'block',
|
||||||
|
top: '0',
|
||||||
|
left: '0',
|
||||||
|
backgroundSize: 'cover',
|
||||||
|
backgroundPosition: 'center center',
|
||||||
|
'&:after': {
|
||||||
|
position: 'absolute',
|
||||||
|
zIndex: 3,
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
content: '""',
|
||||||
|
display: 'block',
|
||||||
|
background: '#000',
|
||||||
|
opacity: '.8'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
list: {
|
||||||
|
marginTop: '20px',
|
||||||
|
paddingLeft: '0',
|
||||||
|
paddingTop: '0',
|
||||||
|
paddingBottom: '0',
|
||||||
|
marginBottom: '0',
|
||||||
|
listStyle: 'none'
|
||||||
|
},
|
||||||
|
item: {
|
||||||
|
position: 'relative',
|
||||||
|
display: 'block',
|
||||||
|
textDecoration: 'none',
|
||||||
|
},
|
||||||
|
itemLink: {
|
||||||
|
width: 'auto',
|
||||||
|
transition: 'all 300ms linear',
|
||||||
|
margin: '10px 15px 0',
|
||||||
|
borderRadius: '3px',
|
||||||
|
position: 'relative',
|
||||||
|
display: 'block',
|
||||||
|
padding: '10px 15px',
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: 'rgba(200, 200, 200, 0.2)'
|
||||||
|
},
|
||||||
|
...defaultFont
|
||||||
|
},
|
||||||
|
itemIcon: {
|
||||||
|
width: '24px',
|
||||||
|
height: '30px',
|
||||||
|
float: 'left',
|
||||||
|
marginRight: '15px',
|
||||||
|
textAlign: 'center',
|
||||||
|
verticalAlign: 'middle',
|
||||||
|
color: 'rgba(255, 255, 255, 0.8)'
|
||||||
|
},
|
||||||
|
itemText: {
|
||||||
|
...defaultFont,
|
||||||
|
margin: '0',
|
||||||
|
lineHeight: '30px',
|
||||||
|
fontSize: '14px',
|
||||||
|
color: '#FFFFFF'
|
||||||
|
},
|
||||||
|
|
||||||
|
whiteFont: {
|
||||||
|
color: '#FFFFFF'
|
||||||
|
},
|
||||||
|
blue: {
|
||||||
|
backgroundColor: infoColor,
|
||||||
|
boxShadow:
|
||||||
|
'0 12px 20px -10px rgba(0,188,212,.28), 0 4px 20px 0 rgba(0,0,0,.12), 0 7px 8px -5px rgba(0,188,212,.2)',
|
||||||
|
'&:hover': {
|
||||||
|
backgroundColor: infoColor,
|
||||||
|
boxShadow:
|
||||||
|
'0 12px 20px -10px rgba(0,188,212,.28), 0 4px 20px 0 rgba(0,0,0,.12), 0 7px 8px -5px rgba(0,188,212,.2)'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
sidebarWrapper: {
|
||||||
|
position: 'relative',
|
||||||
|
height: 'calc(100vh - 75px)',
|
||||||
|
overflow: 'auto',
|
||||||
|
width: drawerWidth,
|
||||||
|
zIndex: 4,
|
||||||
|
overflowScrolling: 'touch'
|
||||||
|
}
|
||||||
|
}) as StyleRules<SidebarStyleKeys>;
|
||||||
|
|
||||||
|
export default sidebarStyle;
|
|
@ -0,0 +1,90 @@
|
||||||
|
import {
|
||||||
|
defaultFont,
|
||||||
|
primaryColor,
|
||||||
|
infoColor,
|
||||||
|
successColor,
|
||||||
|
warningColor,
|
||||||
|
dangerColor
|
||||||
|
} from '../styles';
|
||||||
|
import { WithStyles } from 'material-ui';
|
||||||
|
import { StyleRules } from 'material-ui/styles';
|
||||||
|
|
||||||
|
type TypographyStyleKeys = 'defaultFontStyle' |
|
||||||
|
'defaultHeaderMargins' |
|
||||||
|
'pStyle' |
|
||||||
|
'quote' |
|
||||||
|
'quoteText' |
|
||||||
|
'quoteAuthor' |
|
||||||
|
'mutedText' |
|
||||||
|
'primaryText' |
|
||||||
|
'infoText' |
|
||||||
|
'successText' |
|
||||||
|
'warningText' |
|
||||||
|
'dangerText' |
|
||||||
|
'smallText' |
|
||||||
|
'aStyle';
|
||||||
|
|
||||||
|
export type TypographyStyle = WithStyles<TypographyStyleKeys>;
|
||||||
|
|
||||||
|
const typographyStyle = ({
|
||||||
|
defaultFontStyle: {
|
||||||
|
...defaultFont,
|
||||||
|
fontSize: '14px'
|
||||||
|
},
|
||||||
|
defaultHeaderMargins: {
|
||||||
|
marginTop: '20px',
|
||||||
|
marginBottom: '10px'
|
||||||
|
},
|
||||||
|
pStyle: {
|
||||||
|
margin: '0 0 10px'
|
||||||
|
},
|
||||||
|
quote: {
|
||||||
|
padding: '10px 20px',
|
||||||
|
margin: '0 0 20px',
|
||||||
|
fontSize: '17.5px',
|
||||||
|
borderLeft: '5px solid #eee'
|
||||||
|
},
|
||||||
|
quoteText: {
|
||||||
|
margin: '0 0 10px',
|
||||||
|
fontStyle: 'italic'
|
||||||
|
},
|
||||||
|
quoteAuthor: {
|
||||||
|
display: 'block',
|
||||||
|
fontSize: '80%',
|
||||||
|
lineHeight: '1.42857143',
|
||||||
|
color: '#777'
|
||||||
|
},
|
||||||
|
mutedText: {
|
||||||
|
color: '#777'
|
||||||
|
},
|
||||||
|
primaryText: {
|
||||||
|
color: primaryColor
|
||||||
|
},
|
||||||
|
infoText: {
|
||||||
|
color: infoColor
|
||||||
|
},
|
||||||
|
successText: {
|
||||||
|
color: successColor
|
||||||
|
},
|
||||||
|
warningText: {
|
||||||
|
color: warningColor
|
||||||
|
},
|
||||||
|
dangerText: {
|
||||||
|
color: dangerColor
|
||||||
|
},
|
||||||
|
smallText: {
|
||||||
|
fontSize: '65%',
|
||||||
|
fontWeight: 400,
|
||||||
|
lineHeight: '1',
|
||||||
|
color: '#777'
|
||||||
|
},
|
||||||
|
aStyle: {
|
||||||
|
textDecoration: 'none',
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
'&,&:hover': {
|
||||||
|
color: '#FFFFFF'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) as StyleRules<TypographyStyleKeys>;
|
||||||
|
|
||||||
|
export default typographyStyle;
|
|
@ -0,0 +1,33 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "build/dist",
|
||||||
|
"module": "esnext",
|
||||||
|
"target": "es5",
|
||||||
|
"lib": ["es6", "dom"],
|
||||||
|
"sourceMap": true,
|
||||||
|
"allowJs": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"jsx": "react",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"rootDir": "src",
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"noImplicitThis": true,
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"strictNullChecks": true,
|
||||||
|
"suppressImplicitAnyIndexErrors": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"pretty": true,
|
||||||
|
"typeRoots": ["src/library", "node_modules/@types"]
|
||||||
|
},
|
||||||
|
"exclude": [
|
||||||
|
"node_modules",
|
||||||
|
"build",
|
||||||
|
"scripts",
|
||||||
|
"acceptance-tests",
|
||||||
|
"webpack",
|
||||||
|
"jest",
|
||||||
|
"src/setupTests.ts",
|
||||||
|
"src/neo4jd3.ts"
|
||||||
|
]
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "commonjs"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,97 @@
|
||||||
|
{
|
||||||
|
"extends": ["tslint-react"],
|
||||||
|
"rules": {
|
||||||
|
"align": [
|
||||||
|
true,
|
||||||
|
"parameters",
|
||||||
|
"arguments",
|
||||||
|
"statements"
|
||||||
|
],
|
||||||
|
"ban": false,
|
||||||
|
"class-name": true,
|
||||||
|
"comment-format": [
|
||||||
|
true,
|
||||||
|
"check-space"
|
||||||
|
],
|
||||||
|
"curly": true,
|
||||||
|
"eofline": false,
|
||||||
|
"forin": true,
|
||||||
|
"indent": [ true, "spaces" ],
|
||||||
|
"jsdoc-format": true,
|
||||||
|
"jsx-no-lambda": false,
|
||||||
|
"jsx-no-multiline-js": false,
|
||||||
|
"label-position": true,
|
||||||
|
"max-line-length": [ true, 120 ],
|
||||||
|
"member-ordering": [
|
||||||
|
true,
|
||||||
|
"public-before-private",
|
||||||
|
"static-before-instance",
|
||||||
|
"variables-before-functions"
|
||||||
|
],
|
||||||
|
"no-any": true,
|
||||||
|
"no-arg": true,
|
||||||
|
"no-console": [
|
||||||
|
true,
|
||||||
|
"log",
|
||||||
|
"error",
|
||||||
|
"debug",
|
||||||
|
"info",
|
||||||
|
"time",
|
||||||
|
"timeEnd",
|
||||||
|
"trace"
|
||||||
|
],
|
||||||
|
"no-consecutive-blank-lines": true,
|
||||||
|
"no-construct": true,
|
||||||
|
"no-debugger": true,
|
||||||
|
"no-duplicate-variable": true,
|
||||||
|
"no-empty": true,
|
||||||
|
"no-eval": true,
|
||||||
|
"no-shadowed-variable": true,
|
||||||
|
"no-string-literal": true,
|
||||||
|
"no-switch-case-fall-through": true,
|
||||||
|
"no-trailing-whitespace": false,
|
||||||
|
"no-unused-expression": true,
|
||||||
|
"no-use-before-declare": true,
|
||||||
|
"one-line": [
|
||||||
|
true,
|
||||||
|
"check-catch",
|
||||||
|
"check-else",
|
||||||
|
"check-open-brace",
|
||||||
|
"check-whitespace"
|
||||||
|
],
|
||||||
|
"quotemark": [true, "single", "jsx-double"],
|
||||||
|
"radix": true,
|
||||||
|
"semicolon": [true, "always"],
|
||||||
|
"switch-default": true,
|
||||||
|
|
||||||
|
"trailing-comma": [false],
|
||||||
|
|
||||||
|
"triple-equals": [ true, "allow-null-check" ],
|
||||||
|
"typedef": [
|
||||||
|
true,
|
||||||
|
"parameter",
|
||||||
|
"property-declaration"
|
||||||
|
],
|
||||||
|
"typedef-whitespace": [
|
||||||
|
true,
|
||||||
|
{
|
||||||
|
"call-signature": "nospace",
|
||||||
|
"index-signature": "nospace",
|
||||||
|
"parameter": "nospace",
|
||||||
|
"property-declaration": "nospace",
|
||||||
|
"variable-declaration": "nospace"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"variable-name": [true, "ban-keywords", "check-format", "allow-leading-underscore", "allow-pascal-case"],
|
||||||
|
"whitespace": [
|
||||||
|
true,
|
||||||
|
"check-branch",
|
||||||
|
"check-decl",
|
||||||
|
"check-module",
|
||||||
|
"check-operator",
|
||||||
|
"check-separator",
|
||||||
|
"check-type",
|
||||||
|
"check-typecast"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue