initial commit
This commit is contained in:
parent
f2c2193866
commit
a2e37ff2b6
|
@ -1,73 +1,23 @@
|
|||
# ---> JetBrains
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
# See https://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
# User-specific stuff
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/**/usage.statistics.xml
|
||||
.idea/**/dictionaries
|
||||
.idea/**/shelf
|
||||
# dependencies
|
||||
/node_modules
|
||||
|
||||
# Generated files
|
||||
.idea/**/contentModel.xml
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# Sensitive or high-churn files
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
.idea/**/dbnavigator.xml
|
||||
# production
|
||||
/build
|
||||
|
||||
# Gradle
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
# Gradle and Maven with auto-import
|
||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||
# since they will be recreated, and may cause churn. Uncomment if using
|
||||
# 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
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
.idea/
|
22
README.md
22
README.md
|
@ -1,20 +1,2 @@
|
|||
#### 从命令行创建一个新的仓库
|
||||
|
||||
```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
|
||||
|
||||
```
|
||||
|
||||
# SnowView
|
||||
add a jenkins test
|
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