feat: add .eslintrc and format all code (#11)

This commit is contained in:
Xinhao Yuan 2023-06-26 10:17:56 +08:00 committed by GitHub
parent 7c56c63893
commit 3aa25da75b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 976 additions and 867 deletions

112
web/.eslintrc Normal file
View File

@ -0,0 +1,112 @@
{
"env": {
"browser": true,
"es6": true,
"node": true
},
"parser": "@babel/eslint-parser",
"parserOptions": {
"ecmaVersion": 12,
"sourceType": "module",
"ecmaFeatures": {
"jsx": true
},
"requireConfigFile": false,
"babelOptions": {
"babelrc": false,
"configFile": false,
"presets": ["@babel/preset-react"]
}
},
"settings": {
"react": {
"version": "detect"
}
},
"plugins": ["unused-imports"],
"extends": ["eslint:recommended", "plugin:react/recommended"],
"rules": {
"semi": ["error", "always"],
"indent": ["error", 2],
// follow antd's style guide
"quotes": ["error", "double"],
"jsx-quotes": ["error", "prefer-double"],
"space-in-parens": ["error", "never"],
"object-curly-spacing": ["error", "never"],
"array-bracket-spacing": ["error", "never"],
"comma-spacing": ["error", { "before": false, "after": true }],
"react/jsx-curly-spacing": [
"error",
{ "when": "never", "allowMultiline": true, "children": true }
],
"arrow-spacing": ["error", { "before": true, "after": true }],
"space-before-blocks": ["error", "always"],
"spaced-comment": ["error", "always"],
"react/jsx-tag-spacing": ["error", { "beforeSelfClosing": "always" }],
"block-spacing": ["error", "never"],
"space-before-function-paren": ["error", "never"],
"no-trailing-spaces": ["error", { "ignoreComments": true }],
"eol-last": ["error", "always"],
"no-var": ["error"],
"prefer-const": [
"error",
{
"destructuring": "all"
}
],
"curly": ["error", "all"],
"brace-style": ["error", "1tbs", { "allowSingleLine": true }],
"no-mixed-spaces-and-tabs": "error",
"sort-imports": [
"error",
{
"ignoreDeclarationSort": true
}
],
"no-multiple-empty-lines": [
"error",
{ "max": 1, "maxBOF": 0, "maxEOF": 0 }
],
"space-unary-ops": ["error", { "words": true, "nonwords": false }],
"space-infix-ops": "error",
"key-spacing": ["error", { "beforeColon": false, "afterColon": true }],
"comma-style": ["error", "last"],
"comma-dangle": [
"error",
{
"arrays": "always-multiline",
"objects": "always-multiline",
"imports": "never",
"exports": "never",
"functions": "never"
}
],
"no-multi-spaces": ["error", { "ignoreEOLComments": true }],
"react/no-unknown-property": [
"error",
{
"ignore": ["css"]
}
],
"unused-imports/no-unused-imports": "error",
"unused-imports/no-unused-vars": [
"error",
{
"vars": "all",
"varsIgnorePattern": "^_",
"args": "none",
"argsIgnorePattern": "^_"
}
],
"no-unused-vars": "off",
"react/no-deprecated": "error",
"react/jsx-key": "error",
"no-console": "error",
"eqeqeq": "error",
"keyword-spacing": "error",
"react/prop-types": "off",
"react/display-name": "off",
"react/react-in-jsx-scope": "off",
"no-case-declarations": "off"
}
}

View File

@ -1,8 +1,8 @@
import React, {Component} from 'react'; import React, {Component} from "react";
import {Switch, Redirect, Route, withRouter, Link} from 'react-router-dom'; import {Link, Redirect, Route, Switch, withRouter} from "react-router-dom";
import {Avatar, BackTop, Dropdown, Layout, Menu} from 'antd'; import {Avatar, BackTop, Dropdown, Layout, Menu} from "antd";
import {createFromIconfontCN, DownOutlined, LogoutOutlined, SettingOutlined} from '@ant-design/icons'; import {DownOutlined, LogoutOutlined, SettingOutlined, createFromIconfontCN} from "@ant-design/icons";
import './App.less'; import "./App.less";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import * as AccountBackend from "./backend/AccountBackend"; import * as AccountBackend from "./backend/AccountBackend";
import AuthCallback from "./AuthCallback"; import AuthCallback from "./AuthCallback";
@ -25,7 +25,7 @@ import i18next from "i18next";
const {Header, Footer} = Layout; const {Header, Footer} = Layout;
const IconFont = createFromIconfontCN({ const IconFont = createFromIconfontCN({
scriptUrl: '//at.alicdn.com/t/font_2680620_ffij16fkwdg.js', scriptUrl: "//at.alicdn.com/t/font_2680620_ffij16fkwdg.js",
}); });
class App extends Component { class App extends Component {
@ -61,32 +61,32 @@ class App extends Component {
this.setState({ this.setState({
uri: uri, uri: uri,
}); });
if (uri === '/home') { if (uri === "/home") {
this.setState({selectedMenuKey: '/home'}); this.setState({selectedMenuKey: "/home"});
} else if (uri.includes('/stores')) { } else if (uri.includes("/stores")) {
this.setState({ selectedMenuKey: '/stores' }); this.setState({selectedMenuKey: "/stores"});
} else if (uri.includes('/clustering')) { } else if (uri.includes("/clustering")) {
this.setState({ selectedMenuKey: '/clustering' }); this.setState({selectedMenuKey: "/clustering"});
} else if (uri.includes('/wordsets')) { } else if (uri.includes("/wordsets")) {
this.setState({ selectedMenuKey: '/wordsets' }); this.setState({selectedMenuKey: "/wordsets"});
} else if (uri.includes('/vectorsets')) { } else if (uri.includes("/vectorsets")) {
this.setState({ selectedMenuKey: '/vectorsets' }); this.setState({selectedMenuKey: "/vectorsets"});
} else if (uri.includes('/videos')) { } else if (uri.includes("/videos")) {
this.setState({ selectedMenuKey: '/videos' }); this.setState({selectedMenuKey: "/videos"});
} else { } else {
this.setState({selectedMenuKey: 'null'}); this.setState({selectedMenuKey: "null"});
} }
} }
onUpdateAccount(account) { onUpdateAccount(account) {
this.setState({ this.setState({
account: account account: account,
}); });
} }
setLanguage(account) { setLanguage(account) {
// let language = account?.language; // let language = account?.language;
let language = localStorage.getItem("language"); const language = localStorage.getItem("language");
if (language !== "" && language !== i18next.language) { if (language !== "" && language !== i18next.language) {
Setting.setLanguage(language); Setting.setLanguage(language);
} }
@ -95,7 +95,7 @@ class App extends Component {
getAccount() { getAccount() {
AccountBackend.getAccount() AccountBackend.getAccount()
.then((res) => { .then((res) => {
let account = res.data; const account = res.data;
if (account !== null) { if (account !== null) {
this.setLanguage(account); this.setLanguage(account);
} }
@ -109,12 +109,12 @@ class App extends Component {
signout() { signout() {
AccountBackend.signout() AccountBackend.signout()
.then((res) => { .then((res) => {
if (res.status === 'ok') { if (res.status === "ok") {
this.setState({ this.setState({
account: null account: null,
}); });
Setting.showMessage("success", `Successfully signed out, redirected to homepage`); Setting.showMessage("success", "Successfully signed out, redirected to homepage");
Setting.goToLink("/"); Setting.goToLink("/");
// this.props.history.push("/"); // this.props.history.push("/");
} else { } else {
@ -124,9 +124,9 @@ class App extends Component {
} }
handleRightDropdownClick(e) { handleRightDropdownClick(e) {
if (e.key === '/account') { if (e.key === "/account") {
Setting.openLink(Setting.getMyProfileUrl(this.state.account)); Setting.openLink(Setting.getMyProfileUrl(this.state.account));
} else if (e.key === '/logout') { } else if (e.key === "/logout") {
this.signout(); this.signout();
} }
} }
@ -134,16 +134,16 @@ class App extends Component {
renderAvatar() { renderAvatar() {
if (this.state.account.avatar === "") { if (this.state.account.avatar === "") {
return ( return (
<Avatar style={{ backgroundColor: Setting.getAvatarColor(this.state.account.name), verticalAlign: 'middle' }} size="large"> <Avatar style={{backgroundColor: Setting.getAvatarColor(this.state.account.name), verticalAlign: "middle"}} size="large">
{Setting.getShortName(this.state.account.name)} {Setting.getShortName(this.state.account.name)}
</Avatar> </Avatar>
) );
} else { } else {
return ( return (
<Avatar src={this.state.account.avatar} style={{verticalAlign: 'middle' }} size="large"> <Avatar src={this.state.account.avatar} style={{verticalAlign: "middle"}} size="large">
{Setting.getShortName(this.state.account.name)} {Setting.getShortName(this.state.account.name)}
</Avatar> </Avatar>
) );
} }
} }
@ -163,7 +163,7 @@ class App extends Component {
return ( return (
<Dropdown key="/rightDropDown" overlay={menu} className="rightDropDown"> <Dropdown key="/rightDropDown" overlay={menu} className="rightDropDown">
<div className="ant-dropdown-link" style={{float: 'right', cursor: 'pointer'}}> <div className="ant-dropdown-link" style={{float: "right", cursor: "pointer"}}>
&nbsp; &nbsp;
&nbsp; &nbsp;
{ {
@ -177,24 +177,24 @@ class App extends Component {
&nbsp; &nbsp;
</div> </div>
</Dropdown> </Dropdown>
) );
} }
renderAccount() { renderAccount() {
let res = []; const res = [];
if (this.state.account === undefined) { if (this.state.account === undefined) {
return null; return null;
} else if (this.state.account === null) { } else if (this.state.account === null) {
res.push( res.push(
<Menu.Item key="/signup" style={{float: 'right', marginRight: '20px'}}> <Menu.Item key="/signup" style={{float: "right", marginRight: "20px"}}>
<a href={Setting.getSignupUrl()}> <a href={Setting.getSignupUrl()}>
{i18next.t("account:Sign Up")} {i18next.t("account:Sign Up")}
</a> </a>
</Menu.Item> </Menu.Item>
); );
res.push( res.push(
<Menu.Item key="/signin" style={{float: 'right'}}> <Menu.Item key="/signin" style={{float: "right"}}>
<a href={Setting.getSigninUrl()}> <a href={Setting.getSigninUrl()}>
{i18next.t("account:Sign In")} {i18next.t("account:Sign In")}
</a> </a>
@ -203,19 +203,19 @@ class App extends Component {
} else { } else {
res.push(this.renderRightDropdown()); res.push(this.renderRightDropdown());
return ( return (
<div style={{ float: 'right', margin: '0px', padding: '0px'}}> <div style={{float: "right", margin: "0px", padding: "0px"}}>
{ {
res res
} }
</div> </div>
) );
} }
return res; return res;
} }
renderMenu() { renderMenu() {
let res = []; const res = [];
if (this.state.account === null || this.state.account === undefined) { if (this.state.account === null || this.state.account === undefined) {
return []; return [];
@ -298,7 +298,7 @@ class App extends Component {
renderHomeIfSignedIn(component) { renderHomeIfSignedIn(component) {
if (this.state.account !== null && this.state.account !== undefined) { if (this.state.account !== null && this.state.account !== undefined) {
return <Redirect to='/' /> return <Redirect to="/" />;
} else { } else {
return component; return component;
} }
@ -307,11 +307,10 @@ class App extends Component {
renderSigninIfNotSignedIn(component) { renderSigninIfNotSignedIn(component) {
if (this.state.account === null) { if (this.state.account === null) {
sessionStorage.setItem("from", window.location.pathname); sessionStorage.setItem("from", window.location.pathname);
return <Redirect to='/signin' /> return <Redirect to="/signin" />;
} else if (this.state.account === undefined) { } else if (this.state.account === undefined) {
return null; return null;
} } else {
else {
return component; return component;
} }
} }
@ -319,7 +318,7 @@ class App extends Component {
renderContent() { renderContent() {
return ( return (
<div> <div>
<Header style={{padding: '0', marginBottom: '3px'}}> <Header style={{padding: "0", marginBottom: "3px"}}>
{ {
Setting.isMobile() ? null : ( Setting.isMobile() ? null : (
<Link to={"/"}> <Link to={"/"}>
@ -331,7 +330,7 @@ class App extends Component {
// theme="dark" // theme="dark"
mode={"horizontal"} mode={"horizontal"}
selectedKeys={[`${this.state.selectedMenuKey}`]} selectedKeys={[`${this.state.selectedMenuKey}`]}
style={{lineHeight: '64px'}} style={{lineHeight: "64px"}}
> >
{ {
this.renderMenu() this.renderMenu()
@ -339,8 +338,8 @@ class App extends Component {
{ {
this.renderAccount() this.renderAccount()
} }
<Menu.Item key="en" className="rightDropDown" style={{float: 'right', cursor: 'pointer', marginLeft: '-10px', marginRight: '20px'}}> <Menu.Item key="en" className="rightDropDown" style={{float: "right", cursor: "pointer", marginLeft: "-10px", marginRight: "20px"}}>
<div className="rightDropDown" style={{float: 'right', cursor: 'pointer'}} onClick={() => {Setting.changeLanguage("en");}}> <div className="rightDropDown" style={{float: "right", cursor: "pointer"}} onClick={() => {Setting.changeLanguage("en");}}>
&nbsp;&nbsp;&nbsp;&nbsp;<IconFont type="icon-en" /> &nbsp;&nbsp;&nbsp;&nbsp;<IconFont type="icon-en" />
&nbsp; &nbsp;
English English
@ -348,8 +347,8 @@ class App extends Component {
&nbsp; &nbsp;
</div> </div>
</Menu.Item> </Menu.Item>
<Menu.Item key="zh" className="rightDropDown" style={{float: 'right', cursor: 'pointer'}}> <Menu.Item key="zh" className="rightDropDown" style={{float: "right", cursor: "pointer"}}>
<div className="rightDropDown" style={{float: 'right', cursor: 'pointer'}} onClick={() => {Setting.changeLanguage("zh");}}> <div className="rightDropDown" style={{float: "right", cursor: "pointer"}} onClick={() => {Setting.changeLanguage("zh");}}>
&nbsp;&nbsp;&nbsp;&nbsp;<IconFont type="icon-zh" /> &nbsp;&nbsp;&nbsp;&nbsp;<IconFont type="icon-zh" />
&nbsp; &nbsp;
中文 中文
@ -360,24 +359,24 @@ class App extends Component {
</Menu> </Menu>
</Header> </Header>
<Switch> <Switch>
<Route exact path="/callback" component={AuthCallback}/> <Route exact path="/callback" component={AuthCallback} />
<Route exact path="/signin" render={(props) => this.renderHomeIfSignedIn(<SigninPage {...props} />)}/> <Route exact path="/signin" render={(props) => this.renderHomeIfSignedIn(<SigninPage {...props} />)} />
<Route exact path="/" render={(props) => this.renderSigninIfNotSignedIn(<HomePage account={this.state.account} {...props} />)}/> <Route exact path="/" render={(props) => this.renderSigninIfNotSignedIn(<HomePage account={this.state.account} {...props} />)} />
<Route exact path="/home" render={(props) => this.renderSigninIfNotSignedIn(<HomePage account={this.state.account} {...props} />)}/> <Route exact path="/home" render={(props) => this.renderSigninIfNotSignedIn(<HomePage account={this.state.account} {...props} />)} />
<Route exact path="/stores" render={(props) => this.renderSigninIfNotSignedIn(<StoreListPage account={this.state.account} {...props} />)}/> <Route exact path="/stores" render={(props) => this.renderSigninIfNotSignedIn(<StoreListPage account={this.state.account} {...props} />)} />
<Route exact path="/stores/:owner/:storeName" render={(props) => this.renderSigninIfNotSignedIn(<StoreEditPage account={this.state.account} {...props} />)}/> <Route exact path="/stores/:owner/:storeName" render={(props) => this.renderSigninIfNotSignedIn(<StoreEditPage account={this.state.account} {...props} />)} />
<Route exact path="/stores/:owner/:storeName/view" render={(props) => this.renderSigninIfNotSignedIn(<FileTreePage account={this.state.account} {...props} />)}/> <Route exact path="/stores/:owner/:storeName/view" render={(props) => this.renderSigninIfNotSignedIn(<FileTreePage account={this.state.account} {...props} />)} />
<Route exact path="/clustering" render={(props) => this.renderSigninIfNotSignedIn(<ClusteringPage account={this.state.account} {...props} />)}/> <Route exact path="/clustering" render={(props) => this.renderSigninIfNotSignedIn(<ClusteringPage account={this.state.account} {...props} />)} />
<Route exact path="/wordsets" render={(props) => this.renderSigninIfNotSignedIn(<WordsetListPage account={this.state.account} {...props} />)}/> <Route exact path="/wordsets" render={(props) => this.renderSigninIfNotSignedIn(<WordsetListPage account={this.state.account} {...props} />)} />
<Route exact path="/wordsets/:wordsetName" render={(props) => this.renderSigninIfNotSignedIn(<WordsetEditPage account={this.state.account} {...props} />)}/> <Route exact path="/wordsets/:wordsetName" render={(props) => this.renderSigninIfNotSignedIn(<WordsetEditPage account={this.state.account} {...props} />)} />
<Route exact path="/wordsets/:wordsetName/graph" render={(props) => this.renderSigninIfNotSignedIn(<WordsetGraphPage account={this.state.account} {...props} />)}/> <Route exact path="/wordsets/:wordsetName/graph" render={(props) => this.renderSigninIfNotSignedIn(<WordsetGraphPage account={this.state.account} {...props} />)} />
<Route exact path="/vectorsets" render={(props) => this.renderSigninIfNotSignedIn(<VectorsetListPage account={this.state.account} {...props} />)}/> <Route exact path="/vectorsets" render={(props) => this.renderSigninIfNotSignedIn(<VectorsetListPage account={this.state.account} {...props} />)} />
<Route exact path="/vectorsets/:vectorsetName" render={(props) => this.renderSigninIfNotSignedIn(<VectorsetEditPage account={this.state.account} {...props} />)}/> <Route exact path="/vectorsets/:vectorsetName" render={(props) => this.renderSigninIfNotSignedIn(<VectorsetEditPage account={this.state.account} {...props} />)} />
<Route exact path="/videos" render={(props) => this.renderSigninIfNotSignedIn(<VideoListPage account={this.state.account} {...props} />)}/> <Route exact path="/videos" render={(props) => this.renderSigninIfNotSignedIn(<VideoListPage account={this.state.account} {...props} />)} />
<Route exact path="/videos/:videoName" render={(props) => this.renderSigninIfNotSignedIn(<VideoEditPage account={this.state.account} {...props} />)}/> <Route exact path="/videos/:videoName" render={(props) => this.renderSigninIfNotSignedIn(<VideoEditPage account={this.state.account} {...props} />)} />
</Switch> </Switch>
</div> </div>
) );
} }
renderFooter() { renderFooter() {
@ -387,20 +386,20 @@ class App extends Component {
return ( return (
<Footer id="footer" style={ <Footer id="footer" style={
{ {
borderTop: '1px solid #e8e8e8', borderTop: "1px solid #e8e8e8",
backgroundColor: 'white', backgroundColor: "white",
textAlign: 'center', textAlign: "center",
} }
}> }>
Made with <span style={{color: 'rgb(255, 255, 255)'}}></span> by <a style={{fontWeight: "bold", color: "black"}} target="_blank" rel="noreferrer" href="https://github.com/casbin/casibase">Casibase</a>, { Setting.isMobile() ? "Mobile" : "Desktop" } View Made with <span style={{color: "rgb(255, 255, 255)"}}></span> by <a style={{fontWeight: "bold", color: "black"}} target="_blank" rel="noreferrer" href="https://github.com/casbin/casibase">Casibase</a>, {Setting.isMobile() ? "Mobile" : "Desktop"} View
</Footer> </Footer>
) );
} }
render() { render() {
return ( return (
<div id="parent-area"> <div id="parent-area">
<BackTop/> <BackTop />
<div id="content-wrap"> <div id="content-wrap">
{ {
this.renderContent() this.renderContent()

View File

@ -1,9 +1,9 @@
import React from 'react'; import React from "react";
import ReactDOM from 'react-dom'; import ReactDOM from "react-dom";
import App from './App'; import App from "./App";
it('renders without crashing', () => { it("renders without crashing", () => {
const div = document.createElement('div'); const div = document.createElement("div");
ReactDOM.render(<App />, div); ReactDOM.render(<App />, div);
ReactDOM.unmountComponentAtNode(div); ReactDOM.unmountComponentAtNode(div);
}); });

View File

@ -1,6 +1,6 @@
import React from "react"; import React from "react";
import { Button, Result, Spin } from "antd"; import {Button, Result, Spin} from "antd";
import { withRouter } from "react-router-dom"; import {withRouter} from "react-router-dom";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
class AuthCallback extends React.Component { class AuthCallback extends React.Component {
@ -27,7 +27,7 @@ class AuthCallback extends React.Component {
login() { login() {
Setting.signin().then((res) => { Setting.signin().then((res) => {
if (res.status === "ok") { if (res.status === "ok") {
Setting.showMessage("success", `Logged in successfully`) Setting.showMessage("success", "Logged in successfully");
const link = this.getFromLink(); const link = this.getFromLink();
Setting.goToLink(link); Setting.goToLink(link);
@ -41,15 +41,15 @@ class AuthCallback extends React.Component {
render() { render() {
return ( return (
<div style={{ textAlign: "center" }}> <div style={{textAlign: "center"}}>
{this.state.msg === null ? ( {this.state.msg === null ? (
<Spin <Spin
size="large" size="large"
tip="Signing in..." tip="Signing in..."
style={{ paddingTop: "10%" }} style={{paddingTop: "10%"}}
/> />
) : ( ) : (
<div style={{ display: "inline" }}> <div style={{display: "inline"}}>
<Result <Result
status="error" status="error"
title="Login Error" title="Login Error"

View File

@ -27,8 +27,8 @@ class ClusteringPage extends React.Component {
render() { render() {
return (this.state.wordset === undefined || this.state.wordset === null) ? null : ( return (this.state.wordset === undefined || this.state.wordset === null) ? null : (
<WordsetGraph wordset={this.state.wordset} wordsetName={this.state.wordset.name}/> <WordsetGraph wordset={this.state.wordset} wordsetName={this.state.wordset.name} />
) );
} }
} }

View File

@ -13,4 +13,4 @@ export const DefaultWordsetName = "word";
export const ForceLanguage = ""; export const ForceLanguage = "";
export const DefaultLanguage = "en"; export const DefaultLanguage = "en";
export const AppUrl = "" export const AppUrl = "";

View File

@ -12,13 +12,13 @@ class DataChart extends React.Component {
componentWillMount() { componentWillMount() {
fetch(this.props.url, { fetch(this.props.url, {
method: 'GET', method: "GET",
}).then(res => res.text()) }).then(res => res.text())
.then((text) => { .then((text) => {
if (this.props.filename.startsWith("Impedance_")) { if (this.props.filename.startsWith("Impedance_")) {
this.processImpedanceFile(text); this.processImpedanceFile(text);
} }
}); });
} }
getY(y) { getY(y) {
@ -34,7 +34,7 @@ class DataChart extends React.Component {
lines = lines.filter((line, index) => index % 20 === 0); lines = lines.filter((line, index) => index % 20 === 0);
let data = lines.map(line => { let data = lines.map(line => {
let res = []; const res = [];
const tokens = line.split(" "); const tokens = line.split(" ");
tokens.forEach((token, index) => { tokens.forEach((token, index) => {
if (index !== 0) { if (index !== 0) {
@ -48,7 +48,7 @@ class DataChart extends React.Component {
data = data.map(element => { data = data.map(element => {
element[0] = (new Date("0000-01-01 " + element[0]).getTime() - startTime) / 1000 / 60; element[0] = (new Date("0000-01-01 " + element[0]).getTime() - startTime) / 1000 / 60;
return element; return element;
}) });
this.setState({ this.setState({
data: data, data: data,
@ -62,11 +62,11 @@ class DataChart extends React.Component {
const option = { const option = {
title: { title: {
text: 'Impedance' text: "Impedance",
}, },
tooltip: { tooltip: {
trigger: 'axis', trigger: "axis",
formatter: function (params) { formatter: function(params) {
return JSON.stringify(params.value); return JSON.stringify(params.value);
}, },
axisPointer: { axisPointer: {
@ -74,31 +74,31 @@ class DataChart extends React.Component {
}, },
}, },
xAxis: { xAxis: {
type: 'value', type: "value",
splitLine: { splitLine: {
show: false, show: false,
}, },
}, },
yAxis: { yAxis: {
type: 'value', type: "value",
boundaryGap: [0, '100%'], boundaryGap: [0, "100%"],
splitLine: { splitLine: {
show: false, show: false,
}, },
}, },
series: [ series: [
{ {
name: 'Fake Data', name: "Fake Data",
type: 'line', type: "line",
showSymbol: false, showSymbol: false,
data: this.state.data, data: this.state.data,
}, },
] ],
}; };
return ( return (
<ReactEcharts style={{width: "100%", height: this.props.height, backgroundColor: "white"}} option={option} notMerge={true}/> <ReactEcharts style={{width: "100%", height: this.props.height, backgroundColor: "white"}} option={option} notMerge={true} />
) );
} }
} }

View File

@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import {Button, Popconfirm, Table} from 'antd'; import {Button, Popconfirm, Table} from "antd";
import {DeleteOutlined, DownloadOutlined, FileDoneOutlined} from "@ant-design/icons"; import {DeleteOutlined, DownloadOutlined, FileDoneOutlined} from "@ant-design/icons";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import i18next from "i18next"; import i18next from "i18next";
@ -38,7 +38,7 @@ class FileTable extends React.Component {
} }
addRow(table) { addRow(table) {
let row = {no: table.length, name: `New Vector - ${table.length}`, data: []}; const row = {no: table.length, name: `New Vector - ${table.length}`, data: []};
if (table === undefined) { if (table === undefined) {
table = []; table = [];
} }
@ -66,7 +66,7 @@ class FileTable extends React.Component {
FileBackend.deleteFile(storeId, file.key, isLeaf) FileBackend.deleteFile(storeId, file.key, isLeaf)
.then((res) => { .then((res) => {
if (res === true) { if (res === true) {
Setting.showMessage("success", `File deleted successfully`); Setting.showMessage("success", "File deleted successfully");
this.props.onRefresh(); this.props.onRefresh();
} else { } else {
Setting.showMessage("error", `File failed to delete: ${res}`); Setting.showMessage("error", `File failed to delete: ${res}`);
@ -81,19 +81,19 @@ class FileTable extends React.Component {
const columns = [ const columns = [
{ {
title: i18next.t("vectorset:File name"), title: i18next.t("vectorset:File name"),
dataIndex: 'title', dataIndex: "title",
key: 'title', key: "title",
// width: '200px', // width: '200px',
sorter: (a, b) => a.title.localeCompare(b.title), sorter: (a, b) => a.title.localeCompare(b.title),
render: (text, record, index) => { render: (text, record, index) => {
return text; return text;
} },
}, },
{ {
title: i18next.t("store:Category"), title: i18next.t("store:Category"),
dataIndex: 'isLeaf', dataIndex: "isLeaf",
key: 'isLeaf', key: "isLeaf",
width: '110px', width: "110px",
sorter: (a, b) => a.isLeaf - b.isLeaf, sorter: (a, b) => a.isLeaf - b.isLeaf,
filters: Setting.getDistinctArray(table.map(record => Setting.getFileCategory(record))) filters: Setting.getDistinctArray(table.map(record => Setting.getFileCategory(record)))
.map(fileType => { .map(fileType => {
@ -102,13 +102,13 @@ class FileTable extends React.Component {
onFilter: (value, record) => Setting.getFileCategory(record) === value, onFilter: (value, record) => Setting.getFileCategory(record) === value,
render: (text, record, index) => { render: (text, record, index) => {
return Setting.getFileCategory(record); return Setting.getFileCategory(record);
} },
}, },
{ {
title: i18next.t("store:File type"), title: i18next.t("store:File type"),
dataIndex: 'fileType', dataIndex: "fileType",
key: 'fileType', key: "fileType",
width: '140px', width: "140px",
sorter: (a, b) => Setting.getExtFromPath(a.title).localeCompare(Setting.getExtFromPath(b.title)), sorter: (a, b) => Setting.getExtFromPath(a.title).localeCompare(Setting.getExtFromPath(b.title)),
filters: Setting.getDistinctArray(table.map(record => Setting.getExtFromFile(record))) filters: Setting.getDistinctArray(table.map(record => Setting.getExtFromFile(record)))
.filter(fileType => fileType !== "") .filter(fileType => fileType !== "")
@ -118,13 +118,13 @@ class FileTable extends React.Component {
onFilter: (value, record) => Setting.getExtFromFile(record) === value, onFilter: (value, record) => Setting.getExtFromFile(record) === value,
render: (text, record, index) => { render: (text, record, index) => {
return Setting.getExtFromFile(record); return Setting.getExtFromFile(record);
} },
}, },
{ {
title: i18next.t("vectorset:File size"), title: i18next.t("vectorset:File size"),
dataIndex: 'size', dataIndex: "size",
key: 'size', key: "size",
width: '120px', width: "120px",
sorter: (a, b) => a.size - b.size, sorter: (a, b) => a.size - b.size,
render: (text, record, index) => { render: (text, record, index) => {
if (!record.isLeaf) { if (!record.isLeaf) {
@ -132,38 +132,38 @@ class FileTable extends React.Component {
} }
return Setting.getFriendlyFileSize(text); return Setting.getFriendlyFileSize(text);
} },
}, },
{ {
title: i18next.t("general:Created time"), title: i18next.t("general:Created time"),
dataIndex: 'createdTime', dataIndex: "createdTime",
key: 'createdTime', key: "createdTime",
width: '160px', width: "160px",
sorter: (a, b) => a.createdTime.localeCompare(b.createdTime), sorter: (a, b) => a.createdTime.localeCompare(b.createdTime),
render: (text, record, index) => { render: (text, record, index) => {
return Setting.getFormattedDate(text); return Setting.getFormattedDate(text);
} },
}, },
{ {
title: i18next.t("store:Collected time"), title: i18next.t("store:Collected time"),
dataIndex: 'collectedTime', dataIndex: "collectedTime",
key: 'collectedTime', key: "collectedTime",
width: '160px', width: "160px",
sorter: (a, b) => a.collectedTime.localeCompare(b.collectedTime), sorter: (a, b) => a.collectedTime.localeCompare(b.collectedTime),
render: (text, record, index) => { render: (text, record, index) => {
const collectedTime = Setting.getCollectedTime(record.title); const collectedTime = Setting.getCollectedTime(record.title);
return Setting.getFormattedDate(collectedTime); return Setting.getFormattedDate(collectedTime);
} },
}, },
{ {
title: i18next.t("store:Subject"), title: i18next.t("store:Subject"),
dataIndex: 'subject', dataIndex: "subject",
key: 'subject', key: "subject",
width: '90px', width: "90px",
sorter: (a, b) => a.subject.localeCompare(b.subject), sorter: (a, b) => a.subject.localeCompare(b.subject),
render: (text, record, index) => { render: (text, record, index) => {
return Setting.getSubject(record.title); return Setting.getSubject(record.title);
} },
}, },
// { // {
// title: i18next.t("store:Path"), // title: i18next.t("store:Path"),
@ -206,14 +206,14 @@ class FileTable extends React.Component {
<Button icon={<DeleteOutlined />} style={{marginRight: "10px"}} type="primary" danger size="small">{i18next.t("store:Delete")}</Button> <Button icon={<DeleteOutlined />} style={{marginRight: "10px"}} type="primary" danger size="small">{i18next.t("store:Delete")}</Button>
</Popconfirm> </Popconfirm>
<Button icon={<FileDoneOutlined />} size="small" onClick={() => { <Button icon={<FileDoneOutlined />} size="small" onClick={() => {
let fileKeys = []; const fileKeys = [];
files.forEach((file, index) => { files.forEach((file, index) => {
fileKeys.push(file.key); fileKeys.push(file.key);
}); });
PermissionUtil.addPermission(this.props.account, this.props.store, null, fileKeys); PermissionUtil.addPermission(this.props.account, this.props.store, null, fileKeys);
}}>{i18next.t("store:Add Permission")}</Button> }}>{i18next.t("store:Add Permission")}</Button>
</div> </div>
) );
} }
}} /> }} />
); );
@ -226,7 +226,7 @@ class FileTable extends React.Component {
this.renderTable(this.props.file.children) this.renderTable(this.props.file.children)
} }
</div> </div>
) );
} }
} }

View File

@ -1,11 +1,11 @@
import React from "react"; import React from "react";
import {Button, Col, DatePicker, Descriptions, Empty, Input, Modal, Popconfirm, Radio, Row, Select, Spin, Tooltip, Tree, Upload} from 'antd'; import {Button, Col, DatePicker, Descriptions, Empty, Input, Modal, Popconfirm, Radio, Row, Select, Spin, Tooltip, Tree, Upload} from "antd";
import {CloudUploadOutlined, createFromIconfontCN, DeleteOutlined, DownloadOutlined, FileDoneOutlined, FolderAddOutlined, InfoCircleTwoTone} from "@ant-design/icons"; import {CloudUploadOutlined, DeleteOutlined, DownloadOutlined, FileDoneOutlined, FolderAddOutlined, InfoCircleTwoTone, createFromIconfontCN} from "@ant-design/icons";
import moment from "moment"; import moment from "moment";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import * as FileBackend from "./backend/FileBackend"; import * as FileBackend from "./backend/FileBackend";
import DocViewer, { DocViewerRenderers } from "@cyntler/react-doc-viewer"; import DocViewer, {DocViewerRenderers} from "@cyntler/react-doc-viewer";
import FileViewer from 'react-file-viewer'; import FileViewer from "react-file-viewer";
import i18next from "i18next"; import i18next from "i18next";
import * as PermissionBackend from "./backend/PermissionBackend"; import * as PermissionBackend from "./backend/PermissionBackend";
import * as PermissionUtil from "./PermissionUtil"; import * as PermissionUtil from "./PermissionUtil";
@ -17,11 +17,11 @@ import "codemirror/lib/codemirror.css";
// require("codemirror/theme/material-darker.css"); // require("codemirror/theme/material-darker.css");
// require("codemirror/mode/javascript/javascript"); // require("codemirror/mode/javascript/javascript");
const { Search } = Input; const {Search} = Input;
const { Option } = Select; const {Option} = Select;
const IconFont = createFromIconfontCN({ const IconFont = createFromIconfontCN({
scriptUrl: 'https://cdn.open-ct.com/icon/iconfont.js', scriptUrl: "https://cdn.open-ct.com/icon/iconfont.js",
}); });
class FileTree extends React.Component { class FileTree extends React.Component {
@ -29,7 +29,7 @@ class FileTree extends React.Component {
super(props); super(props);
this.state = { this.state = {
classes: props, classes: props,
expandedKeys: ['0-0', '0-0-0', '0-0-0-0'], expandedKeys: ["0-0", "0-0-0", "0-0-0-0"],
checkedKeys: [], checkedKeys: [],
checkedFiles: [], checkedFiles: [],
selectedKeys: [], selectedKeys: [],
@ -55,7 +55,7 @@ class FileTree extends React.Component {
} }
getPermissionMap(permissions) { getPermissionMap(permissions) {
let permissionMap = {}; const permissionMap = {};
permissions.forEach((permission, index) => { permissions.forEach((permission, index) => {
if (permissionMap[permission.resources[0]] === undefined) { if (permissionMap[permission.resources[0]] === undefined) {
permissionMap[permission.resources[0]] = []; permissionMap[permission.resources[0]] = [];
@ -98,14 +98,14 @@ class FileTree extends React.Component {
{" " + i18next.t("store:Please choose the type of your data")} {" " + i18next.t("store:Please choose the type of your data")}
</div> </div>
} }
visible={this.state.isUploadFileModalVisible} visible={this.state.isUploadFileModalVisible}
onCancel={() => { onCancel={() => {
this.setState({ this.setState({
isUploadFileModalVisible: false, isUploadFileModalVisible: false,
}); });
}} }}
width={"360px"} width={"360px"}
footer={null} > footer={null} >
<Radio.Group buttonStyle="solid" onChange={e => { <Radio.Group buttonStyle="solid" onChange={e => {
this.setState({ this.setState({
uploadFileType: e.target.value, uploadFileType: e.target.value,
@ -116,7 +116,7 @@ class FileTree extends React.Component {
console.log(this.state.file); console.log(this.state.file);
console.log(this.state.info); console.log(this.state.info);
let newInfo = Setting.deepCopy(this.state.info); const newInfo = Setting.deepCopy(this.state.info);
if (uploadFileType !== "Other") { if (uploadFileType !== "Other") {
for (let i = 0; i < newInfo.fileList.length; i++) { for (let i = 0; i < newInfo.fileList.length; i++) {
const filename = newInfo.fileList[i].name; const filename = newInfo.fileList[i].name;
@ -129,11 +129,11 @@ class FileTree extends React.Component {
}} value={this.state.uploadFileType}> }} value={this.state.uploadFileType}>
<Radio.Button value={"ECG"}>ECG</Radio.Button> <Radio.Button value={"ECG"}>ECG</Radio.Button>
<Radio.Button value={"Impedance"}>Impedance</Radio.Button> <Radio.Button value={"Impedance"}>Impedance</Radio.Button>
{/*<Radio.Button value={"EEG"}>EEG</Radio.Button>*/} {/* <Radio.Button value={"EEG"}>EEG</Radio.Button>*/}
<Radio.Button value={"Other"}>{i18next.t("store:Other")}</Radio.Button> <Radio.Button value={"Other"}>{i18next.t("store:Other")}</Radio.Button>
</Radio.Group> </Radio.Group>
</Modal> </Modal>
) );
} }
uploadFile(file, info) { uploadFile(file, info) {
@ -152,19 +152,19 @@ class FileTree extends React.Component {
Promise.all(promises) Promise.all(promises)
.then((values) => { .then((values) => {
Setting.showMessage("success", `File uploaded successfully`); Setting.showMessage("success", "File uploaded successfully");
this.props.onRefresh(); this.props.onRefresh();
}) })
.catch(error => { .catch(error => {
Setting.showMessage("error", `File failed to upload: ${error}`); Setting.showMessage("error", `File failed to upload: ${error}`);
}); });
}; }
addFile(file, newFolder) { addFile(file, newFolder) {
const storeId = `${this.props.store.owner}/${this.props.store.name}`; const storeId = `${this.props.store.owner}/${this.props.store.name}`;
FileBackend.addFile(storeId, file.key, false, newFolder, null) FileBackend.addFile(storeId, file.key, false, newFolder, null)
.then((res) => { .then((res) => {
Setting.showMessage("success", `File added successfully`); Setting.showMessage("success", "File added successfully");
this.props.onRefresh(); this.props.onRefresh();
}) })
.catch(error => { .catch(error => {
@ -177,7 +177,7 @@ class FileTree extends React.Component {
FileBackend.deleteFile(storeId, file.key, isLeaf) FileBackend.deleteFile(storeId, file.key, isLeaf)
.then((res) => { .then((res) => {
if (res === true) { if (res === true) {
Setting.showMessage("success", `File deleted successfully`); Setting.showMessage("success", "File deleted successfully");
this.props.onRefresh(); this.props.onRefresh();
} else { } else {
Setting.showMessage("error", `File failed to delete: ${res}`); Setting.showMessage("error", `File failed to delete: ${res}`);
@ -199,9 +199,9 @@ class FileTree extends React.Component {
return ( return (
<span key={permission.name} <span key={permission.name}
onClick={(e) => { onClick={(e) => {
Setting.openLink(Setting.getMyProfileUrl(this.props.account).replace("/account", `/permissions/${permission.owner}/${permission.name}`)); Setting.openLink(Setting.getMyProfileUrl(this.props.account).replace("/account", `/permissions/${permission.owner}/${permission.name}`));
e.stopPropagation(); e.stopPropagation();
}} }}
> >
{ {
permission.users.map(user => { permission.users.map(user => {
@ -212,11 +212,11 @@ class FileTree extends React.Component {
Setting.getTag(username, permission.actions[0], permission.state) Setting.getTag(username, permission.actions[0], permission.state)
} }
</span> </span>
) );
}) })
} }
</span> </span>
) );
} }
renderPermissions(permissions, isReadable) { renderPermissions(permissions, isReadable) {
@ -263,11 +263,11 @@ class FileTree extends React.Component {
} }
isFileReadable(file) { isFileReadable(file) {
return this.isFileOk(file, "Read") return this.isFileOk(file, "Read");
} }
isFileWritable(file) { isFileWritable(file) {
return this.isFileOk(file, "Write") return this.isFileOk(file, "Write");
} }
isFileAdmin(file) { isFileAdmin(file) {
@ -275,7 +275,7 @@ class FileTree extends React.Component {
return true; return true;
} }
return this.isFileOk(file, "Admin") return this.isFileOk(file, "Admin");
} }
renderSearch() { renderSearch() {
@ -287,7 +287,7 @@ class FileTree extends React.Component {
selectedFile: null, selectedFile: null,
}); });
}} /> }} />
) );
} }
getCacheApp(filename) { getCacheApp(filename) {
@ -317,7 +317,7 @@ class FileTree extends React.Component {
loading: true, loading: true,
}); });
fetch(url, {method: 'GET'}) fetch(url, {method: "GET"})
.then(res => res.text()) .then(res => res.text())
.then(res => { .then(res => {
this.setState({ this.setState({
@ -327,7 +327,7 @@ class FileTree extends React.Component {
}); });
} }
} }
} };
const key = info.node.key; const key = info.node.key;
const filename = info.node.title; const filename = info.node.title;
@ -418,31 +418,31 @@ class FileTree extends React.Component {
{ {
!isWritable ? null : ( !isWritable ? null : (
<React.Fragment> <React.Fragment>
{/*<Tooltip title={i18next.t("store:Rename")}>*/} {/* <Tooltip title={i18next.t("store:Rename")}>*/}
{/* <Button style={{marginRight: "5px"}} icon={<EditOutlined />} size="small" onClick={(e) => {*/} {/* <Button style={{marginRight: "5px"}} icon={<EditOutlined />} size="small" onClick={(e) => {*/}
{/* Setting.showMessage("error", "Rename");*/} {/* Setting.showMessage("error", "Rename");*/}
{/* e.stopPropagation();*/} {/* e.stopPropagation();*/}
{/* }} />*/} {/* }} />*/}
{/*</Tooltip>*/} {/* </Tooltip>*/}
{/*<Tooltip title={i18next.t("store:Move")}>*/} {/* <Tooltip title={i18next.t("store:Move")}>*/}
{/* <Button style={{marginRight: "5px"}} icon={<RadiusSettingOutlined />} size="small" onClick={(e) => {*/} {/* <Button style={{marginRight: "5px"}} icon={<RadiusSettingOutlined />} size="small" onClick={(e) => {*/}
{/* Setting.showMessage("error", "Move");*/} {/* Setting.showMessage("error", "Move");*/}
{/* e.stopPropagation();*/} {/* e.stopPropagation();*/}
{/* }} />*/} {/* }} />*/}
{/*</Tooltip>*/} {/* </Tooltip>*/}
<Tooltip title={i18next.t("store:Delete")}> <Tooltip title={i18next.t("store:Delete")}>
<span onClick={(e) => e.stopPropagation()}> <span onClick={(e) => e.stopPropagation()}>
<Popconfirm <Popconfirm
title={`Sure to delete file: ${file.title} ?`} title={`Sure to delete file: ${file.title} ?`}
onConfirm={(e) => { onConfirm={(e) => {
this.deleteFile(file, true); this.deleteFile(file, true);
}} }}
okText="OK" okText="OK"
cancelText="Cancel" cancelText="Cancel"
> >
<Button style={{marginRight: "5px"}} icon={<DeleteOutlined />} size="small" /> <Button style={{marginRight: "5px"}} icon={<DeleteOutlined />} size="small" />
</Popconfirm> </Popconfirm>
</span> </span>
</Tooltip> </Tooltip>
</React.Fragment> </React.Fragment>
) )
@ -465,7 +465,7 @@ class FileTree extends React.Component {
(this.state.permissionMap === null) ? null : this.renderPermissions(this.state.permissionMap[file.key], isReadable) (this.state.permissionMap === null) ? null : this.renderPermissions(this.state.permissionMap[file.key], isReadable)
} }
</Tooltip> </Tooltip>
) );
} else { } else {
return ( return (
<Tooltip color={"rgb(255,255,255,0.8)"} placement="right" title={ <Tooltip color={"rgb(255,255,255,0.8)"} placement="right" title={
@ -515,8 +515,8 @@ class FileTree extends React.Component {
} }
}} }}
> >
<Button style={{marginRight: "5px"}} icon={<CloudUploadOutlined />} size="small" /> <Button style={{marginRight: "5px"}} icon={<CloudUploadOutlined />} size="small" />
</Upload> </Upload>
</span> </span>
</Tooltip> </Tooltip>
{ {
@ -558,35 +558,35 @@ class FileTree extends React.Component {
(this.state.permissionMap === null) ? null : this.renderPermissions(this.state.permissionMap[file.key], isReadable) (this.state.permissionMap === null) ? null : this.renderPermissions(this.state.permissionMap[file.key], isReadable)
} }
</Tooltip> </Tooltip>
) );
} }
}} }}
icon={(file) => { icon={(file) => {
if (file.isLeaf) { if (file.isLeaf) {
const ext = Setting.getExtFromPath(file.data.key); const ext = Setting.getExtFromPath(file.data.key);
if (ext === "pdf") { if (ext === "pdf") {
return <IconFont type='icon-testpdf' /> return <IconFont type="icon-testpdf" />;
} else if (ext === "doc" || ext === "docx") { } else if (ext === "doc" || ext === "docx") {
return <IconFont type='icon-testdocx' /> return <IconFont type="icon-testdocx" />;
} else if (ext === "ppt" || ext === "pptx") { } else if (ext === "ppt" || ext === "pptx") {
return <IconFont type='icon-testpptx' /> return <IconFont type="icon-testpptx" />;
} else if (ext === "xls" || ext === "xlsx") { } else if (ext === "xls" || ext === "xlsx") {
return <IconFont type='icon-testxlsx' /> return <IconFont type="icon-testxlsx" />;
} else if (ext === "txt") { } else if (ext === "txt") {
return <IconFont type='icon-testdocument' /> return <IconFont type="icon-testdocument" />;
} else if (ext === "png" || ext === "bmp" || ext === "jpg" || ext === "jpeg" || ext === "svg") { } else if (ext === "png" || ext === "bmp" || ext === "jpg" || ext === "jpeg" || ext === "svg") {
return <IconFont type='icon-testPicture' /> return <IconFont type="icon-testPicture" />;
} else if (ext === "html") { } else if (ext === "html") {
return <IconFont type='icon-testhtml' /> return <IconFont type="icon-testhtml" />;
} else if (ext === "js") { } else if (ext === "js") {
return <IconFont type='icon-testjs' /> return <IconFont type="icon-testjs" />;
} else if (ext === "css") { } else if (ext === "css") {
return <IconFont type='icon-testcss' /> return <IconFont type="icon-testcss" />;
} else { } else {
return <IconFont type='icon-testfile-unknown' /> return <IconFont type="icon-testfile-unknown" />;
} }
} else { } else {
return <IconFont type='icon-testfolder' /> return <IconFont type="icon-testfolder" />;
} }
}} }}
/> />
@ -606,7 +606,7 @@ class FileTree extends React.Component {
const outerFile = {children: this.state.checkedFiles}; const outerFile = {children: this.state.checkedFiles};
return ( return (
<FileTable account={this.props.account} store={this.props.store} onRefresh={() => this.props.onRefresh()} file={outerFile} isCheckMode={true} /> <FileTable account={this.props.account} store={this.props.store} onRefresh={() => this.props.onRefresh()} file={outerFile} isCheckMode={true} />
) );
} }
if (this.state.selectedKeys.length === 0) { if (this.state.selectedKeys.length === 0) {
@ -624,7 +624,7 @@ class FileTree extends React.Component {
if (!file.isLeaf) { if (!file.isLeaf) {
return ( return (
<FileTable account={this.props.account} store={this.props.store} onRefresh={() => this.props.onRefresh()} file={file} isCheckMode={false} /> <FileTable account={this.props.account} store={this.props.store} onRefresh={() => this.props.onRefresh()} file={file} isCheckMode={false} />
) );
} }
if (!filename.includes(".")) { if (!filename.includes(".")) {
@ -643,7 +643,7 @@ class FileTree extends React.Component {
return ( return (
<iframe key={path} title={app} src={`${Conf.AppUrl}${app}`} width={"100%"} height={"100%"} /> <iframe key={path} title={app} src={`${Conf.AppUrl}${app}`} width={"100%"} height={"100%"} />
// <DataChart filename={filename} url={url} height={this.getEditorHeightCss()} /> // <DataChart filename={filename} url={url} height={this.getEditorHeightCss()} />
) );
} else if (this.isExtForDocViewer(ext)) { } else if (this.isExtForDocViewer(ext)) {
// https://github.com/Alcumus/react-doc-viewer // https://github.com/Alcumus/react-doc-viewer
return ( return (
@ -665,8 +665,8 @@ class FileTree extends React.Component {
header: { header: {
disableHeader: true, disableHeader: true,
disableFileName: true, disableFileName: true,
retainURLParams: false retainURLParams: false,
} },
}} }}
/> />
); );
@ -718,7 +718,7 @@ class FileTree extends React.Component {
} }
setPropertyValue(file, propertyName, value) { setPropertyValue(file, propertyName, value) {
let store = this.props.store; const store = this.props.store;
if (store.propertiesMap[file.key] === undefined) { if (store.propertiesMap[file.key] === undefined) {
store.propertiesMap[file.key] = {}; store.propertiesMap[file.key] = {};
} }
@ -728,7 +728,7 @@ class FileTree extends React.Component {
getMomentTime(t) { getMomentTime(t) {
if (t === "") { if (t === "") {
return "" return "";
} else { } else {
return new moment(t); return new moment(t);
} }
@ -803,7 +803,7 @@ class FileTree extends React.Component {
</Descriptions.Item> </Descriptions.Item>
</Descriptions> </Descriptions>
</div> </div>
) );
} }
getEditorHeightCss() { getEditorHeightCss() {
@ -841,7 +841,7 @@ class FileTree extends React.Component {
this.renderUploadFileModal() this.renderUploadFileModal()
} }
</div> </div>
) );
} }
} }

View File

@ -12,9 +12,9 @@ class HomePage extends React.Component {
render() { render() {
if (this.props.account.tag === "Video") { if (this.props.account.tag === "Video") {
return <Redirect to="/videos" /> return <Redirect to="/videos" />;
} else { } else {
return <FileTreePage account={this.props.account} /> return <FileTreePage account={this.props.account} />;
} }
} }
} }

View File

@ -1,6 +1,6 @@
import React from "react"; import React from "react";
import {DeleteOutlined} from '@ant-design/icons'; import {DeleteOutlined} from "@ant-design/icons";
import {Button, Col, Input, InputNumber, Row, Table, Tooltip} from 'antd'; import {Button, Col, Input, InputNumber, Row, Table, Tooltip} from "antd";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import i18next from "i18next"; import i18next from "i18next";
import FileSaver from "file-saver"; import FileSaver from "file-saver";
@ -49,7 +49,7 @@ class LabelTable extends React.Component {
return; return;
} }
let row = {id: Setting.getRandomName(), startTime: currentTime, endTime: Setting.toFixed(currentTime + 1, 3), text: ""}; const row = {id: Setting.getRandomName(), startTime: currentTime, endTime: Setting.toFixed(currentTime + 1, 3), text: ""};
if (table === undefined) { if (table === undefined) {
table = []; table = [];
} }
@ -74,9 +74,9 @@ class LabelTable extends React.Component {
} }
downloadLabels(table) { downloadLabels(table) {
let data = []; const data = [];
table.forEach((label, i) => { table.forEach((label, i) => {
let row = {}; const row = {};
row[0] = label.startTime; row[0] = label.startTime;
row[1] = label.endTime; row[1] = label.endTime;
@ -84,7 +84,7 @@ class LabelTable extends React.Component {
data.push(row); data.push(row);
}); });
let sheet = XLSX.utils.json_to_sheet(data, {skipHeader: true}); const sheet = XLSX.utils.json_to_sheet(data, {skipHeader: true});
const blob = Setting.sheet2blob(sheet, "labels"); const blob = Setting.sheet2blob(sheet, "labels");
const fileName = `labels-${this.props.video.name}-${table.length}.xlsx`; const fileName = `labels-${this.props.video.name}-${table.length}.xlsx`;
FileSaver.saveAs(blob, fileName); FileSaver.saveAs(blob, fileName);
@ -94,9 +94,9 @@ class LabelTable extends React.Component {
const columns = [ const columns = [
{ {
title: i18next.t("general:No."), title: i18next.t("general:No."),
dataIndex: 'no', dataIndex: "no",
key: 'no', key: "no",
width: '70px', width: "70px",
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Button type={"text"} style={{width: "50px"}} onClick={() => { <Button type={"text"} style={{width: "50px"}} onClick={() => {
@ -106,72 +106,72 @@ class LabelTable extends React.Component {
}} > }} >
{index + 1} {index + 1}
</Button> </Button>
) );
} },
}, },
{ {
title: i18next.t("video:Start time (s)"), title: i18next.t("video:Start time (s)"),
dataIndex: 'startTime', dataIndex: "startTime",
key: 'startTime', key: "startTime",
width: '120px', width: "120px",
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<InputNumber style={{width: "100%"}} min={0} value={text} onChange={value => { <InputNumber style={{width: "100%"}} min={0} value={text} onChange={value => {
this.updateField(table, index, 'startTime', value); this.updateField(table, index, "startTime", value);
if (record.endTime <= value) { if (record.endTime <= value) {
this.updateField(table, index, 'endTime', Setting.toFixed(value + 1, 3)); this.updateField(table, index, "endTime", Setting.toFixed(value + 1, 3));
} }
this.reorderTable(table); this.reorderTable(table);
}} /> }} />
) );
} },
}, },
{ {
title: i18next.t("video:End time (s)"), title: i18next.t("video:End time (s)"),
dataIndex: 'endTime', dataIndex: "endTime",
key: 'endTime', key: "endTime",
width: '120px', width: "120px",
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<InputNumber style={{width: "100%"}} min={record.startTime} value={text} onChange={value => { <InputNumber style={{width: "100%"}} min={record.startTime} value={text} onChange={value => {
this.updateField(table, index, 'endTime', value); this.updateField(table, index, "endTime", value);
this.reorderTable(table); this.reorderTable(table);
}} /> }} />
) );
} },
}, },
{ {
title: i18next.t("video:Text"), title: i18next.t("video:Text"),
dataIndex: 'text', dataIndex: "text",
key: 'text', key: "text",
// width: '200px', // width: '200px',
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Input value={text} onChange={e => { <Input value={text} onChange={e => {
this.updateField(table, index, 'text', e.target.value); this.updateField(table, index, "text", e.target.value);
}} /> }} />
) );
} },
}, },
{ {
title: i18next.t("general:Action"), title: i18next.t("general:Action"),
key: 'action', key: "action",
width: '50px', width: "50px",
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<div> <div>
{/*<Tooltip placement="bottomLeft" title={"Up"}>*/} {/* <Tooltip placement="bottomLeft" title={"Up"}>*/}
{/* <Button style={{marginRight: "5px"}} disabled={index === 0} icon={<UpOutlined />} size="small" onClick={() => this.upRow(table, index)} />*/} {/* <Button style={{marginRight: "5px"}} disabled={index === 0} icon={<UpOutlined />} size="small" onClick={() => this.upRow(table, index)} />*/}
{/*</Tooltip>*/} {/* </Tooltip>*/}
{/*<Tooltip placement="topLeft" title={"Down"}>*/} {/* <Tooltip placement="topLeft" title={"Down"}>*/}
{/* <Button style={{marginRight: "5px"}} disabled={index === table.length - 1} icon={<DownOutlined />} size="small" onClick={() => this.downRow(table, index)} />*/} {/* <Button style={{marginRight: "5px"}} disabled={index === table.length - 1} icon={<DownOutlined />} size="small" onClick={() => this.downRow(table, index)} />*/}
{/*</Tooltip>*/} {/* </Tooltip>*/}
<Tooltip placement="right" title={"Delete"}> <Tooltip placement="right" title={"Delete"}>
<Button icon={<DeleteOutlined />} size="small" onClick={() => this.deleteRow(table, index)} /> <Button icon={<DeleteOutlined />} size="small" onClick={() => this.deleteRow(table, index)} />
</Tooltip> </Tooltip>
</div> </div>
); );
} },
}, },
]; ];
@ -188,20 +188,20 @@ class LabelTable extends React.Component {
return ( return (
<Table rowKey={"id"} columns={columns} dataSource={table} size="middle" bordered pagination={false} <Table rowKey={"id"} columns={columns} dataSource={table} size="middle" bordered pagination={false}
title={() => ( title={() => (
<div> <div>
{this.props.title}&nbsp;&nbsp;&nbsp;&nbsp; {this.props.title}&nbsp;&nbsp;&nbsp;&nbsp;
<Button style={{marginRight: "5px"}} type="primary" size="small" onClick={() => this.addRow(table)}>{i18next.t("general:Add")}</Button> <Button style={{marginRight: "5px"}} type="primary" size="small" onClick={() => this.addRow(table)}>{i18next.t("general:Add")}</Button>
{ {
table.length === 0 ? null : ( table.length === 0 ? null : (
<Button style={{marginLeft: "5px", marginRight: "5px"}} size="small" onClick={() => this.downloadLabels(table)}>{i18next.t("general:Download")}</Button> <Button style={{marginLeft: "5px", marginRight: "5px"}} size="small" onClick={() => this.downloadLabels(table)}>{i18next.t("general:Download")}</Button>
) )
} }
</div> </div>
)} )}
rowClassName={(record, index) => { rowClassName={(record, index) => {
return (highlightIndex === index) ? "alert-row" : ""; return (highlightIndex === index) ? "alert-row" : "";
}} }}
/> />
); );
} }
@ -209,7 +209,7 @@ class LabelTable extends React.Component {
render() { render() {
return ( return (
<div> <div>
<Row style={{marginTop: '10px'}} > <Row style={{marginTop: "10px"}} >
<Col span={24}> <Col span={24}>
{ {
this.renderTable(this.props.table) this.renderTable(this.props.table)
@ -217,7 +217,7 @@ class LabelTable extends React.Component {
</Col> </Col>
</Row> </Row>
</div> </div>
) );
} }
} }

View File

@ -35,8 +35,8 @@ export function addPermission(account, store, file = null, fileKeys = null) {
PermissionBackend.addPermission(newPermission) PermissionBackend.addPermission(newPermission)
.then((res) => { .then((res) => {
Setting.openLink(Setting.getMyProfileUrl(account).replace("/account", `/permissions/${newPermission.owner}/${newPermission.name}`)); Setting.openLink(Setting.getMyProfileUrl(account).replace("/account", `/permissions/${newPermission.owner}/${newPermission.name}`));
} }
) )
.catch(error => { .catch(error => {
Setting.showMessage("error", `Permission failed to add: ${error}`); Setting.showMessage("error", `Permission failed to add: ${error}`);

View File

@ -1,4 +1,4 @@
import {message, Tag, Tooltip} from "antd"; import {Tag, Tooltip, message} from "antd";
import {SyncOutlined} from "@ant-design/icons"; import {SyncOutlined} from "@ant-design/icons";
import {isMobile as isMobileDevice} from "react-device-detect"; import {isMobile as isMobileDevice} from "react-device-detect";
import i18next from "i18next"; import i18next from "i18next";
@ -8,12 +8,12 @@ import XLSX from "xlsx";
import moment from "moment/moment"; import moment from "moment/moment";
import * as StoreBackend from "./backend/StoreBackend"; import * as StoreBackend from "./backend/StoreBackend";
export let ServerUrl = ''; export let ServerUrl = "";
export let CasdoorSdk; export let CasdoorSdk;
export function initServerUrl() { export function initServerUrl() {
const hostname = window.location.hostname; const hostname = window.location.hostname;
if (hostname === 'localhost') { if (hostname === "localhost") {
ServerUrl = `http://${hostname}:14000`; ServerUrl = `http://${hostname}:14000`;
} }
} }
@ -65,7 +65,7 @@ export function myParseInt(i) {
export function openLink(link) { export function openLink(link) {
// this.props.history.push(link); // this.props.history.push(link);
const w = window.open('about:blank'); const w = window.open("about:blank");
w.location.href = link; w.location.href = link;
} }
@ -134,11 +134,9 @@ export function trim(str, ch) {
let start = 0; let start = 0;
let end = str.length; let end = str.length;
while(start < end && str[start] === ch) while (start < end && str[start] === ch) {++start;}
++start;
while(end > start && str[end - 1] === ch) while (end > start && str[end - 1] === ch) {--end;}
--end;
return (start > 0 || end < str.length) ? str.substring(start, end) : str; return (start > 0 || end < str.length) ? str.substring(start, end) : str;
} }
@ -153,8 +151,8 @@ export function getFormattedDate(date) {
return null; return null;
} }
date = date.replace('T', ' '); date = date.replace("T", " ");
date = date.replace('+08:00', ' '); date = date.replace("+08:00", " ");
return date; return date;
} }
@ -163,10 +161,10 @@ export function getFormattedDateShort(date) {
} }
export function getShortName(s) { export function getShortName(s) {
return s.split('/').slice(-1)[0]; return s.split("/").slice(-1)[0];
} }
export function getShortText(s, maxLength=35) { export function getShortText(s, maxLength = 35) {
if (s.length > maxLength) { if (s.length > maxLength) {
return `${s.slice(0, maxLength)}...`; return `${s.slice(0, maxLength)}...`;
} else { } else {
@ -178,7 +176,7 @@ function getRandomInt(s) {
let hash = 0; let hash = 0;
if (s.length !== 0) { if (s.length !== 0) {
for (let i = 0; i < s.length; i++) { for (let i = 0; i < s.length; i++) {
let char = s.charCodeAt(i); const char = s.charCodeAt(i);
hash = ((hash << 5) - hash) + char; hash = ((hash << 5) - hash) + char;
hash = hash & hash; hash = hash & hash;
} }
@ -188,7 +186,7 @@ function getRandomInt(s) {
} }
export function getAvatarColor(s) { export function getAvatarColor(s) {
const colorList = ['#f56a00', '#7265e6', '#ffbf00', '#00a2ae']; const colorList = ["#f56a00", "#7265e6", "#ffbf00", "#00a2ae"];
let random = getRandomInt(s); let random = getRandomInt(s);
if (random < 0) { if (random < 0) {
random = -random; random = -random;
@ -241,7 +239,7 @@ export function getTag(text, type, state) {
let icon = null; let icon = null;
let style = {}; let style = {};
if (state === "Pending") { if (state === "Pending") {
icon = <SyncOutlined spin /> icon = <SyncOutlined spin />;
style = {borderStyle: "dashed", backgroundColor: "white"}; style = {borderStyle: "dashed", backgroundColor: "white"};
} }
@ -252,7 +250,7 @@ export function getTag(text, type, state) {
{text} {text}
</Tag> </Tag>
</Tooltip> </Tooltip>
) );
} else if (type === "Write") { } else if (type === "Write") {
return ( return (
<Tooltip placement="top" title={"Write"}> <Tooltip placement="top" title={"Write"}>
@ -260,7 +258,7 @@ export function getTag(text, type, state) {
{text} {text}
</Tag> </Tag>
</Tooltip> </Tooltip>
) );
} else if (type === "Admin") { } else if (type === "Admin") {
return ( return (
<Tooltip placement="top" title={"Admin"}> <Tooltip placement="top" title={"Admin"}>
@ -268,7 +266,7 @@ export function getTag(text, type, state) {
{text} {text}
</Tag> </Tag>
</Tooltip> </Tooltip>
) );
} else { } else {
return null; return null;
} }
@ -279,7 +277,7 @@ export function getTags(vectors) {
return []; return [];
} }
let res = []; const res = [];
vectors.forEach((vector, i) => { vectors.forEach((vector, i) => {
if (vector.data.length !== 0) { if (vector.data.length !== 0) {
res.push( res.push(
@ -305,7 +303,7 @@ export function getLabelTags(labels) {
return []; return [];
} }
let res = []; const res = [];
labels.forEach((label, i) => { labels.forEach((label, i) => {
res.push( res.push(
<Tooltip placement="top" title={getShortText(JSON.stringify(label.text), 500)}> <Tooltip placement="top" title={getShortText(JSON.stringify(label.text), 500)}>
@ -329,7 +327,7 @@ export function getPercentage(f) {
function s2ab(s) { function s2ab(s) {
const buf = new ArrayBuffer(s.length); const buf = new ArrayBuffer(s.length);
const view = new Uint8Array(buf); const view = new Uint8Array(buf);
for (let i = 0; i !== s.length; i ++) { for (let i = 0; i !== s.length; i++) {
view[i] = s.charCodeAt(i) & 0xFF; view[i] = s.charCodeAt(i) & 0xFF;
} }
return buf; return buf;
@ -355,9 +353,9 @@ export function workbook2blob(workbook) {
} }
export function downloadXlsx(wordset) { export function downloadXlsx(wordset) {
let data = []; const data = [];
wordset.vectors.forEach((vector, i) => { wordset.vectors.forEach((vector, i) => {
let row = {}; const row = {};
row[0] = vector.name; row[0] = vector.name;
vector.data.forEach((dataItem, i) => { vector.data.forEach((dataItem, i) => {
@ -367,7 +365,7 @@ export function downloadXlsx(wordset) {
data.push(row); data.push(row);
}); });
let sheet = XLSX.utils.json_to_sheet(data, {skipHeader: true}); const sheet = XLSX.utils.json_to_sheet(data, {skipHeader: true});
// sheet["!cols"] = [ // sheet["!cols"] = [
// { wch: 18 }, // { wch: 18 },
// { wch: 7 }, // { wch: 7 },
@ -391,11 +389,11 @@ export function getFriendlyFileSize(size) {
return size + " B"; return size + " B";
} }
let i = Math.floor(Math.log(size) / Math.log(1024)); const i = Math.floor(Math.log(size) / Math.log(1024));
let num = (size / Math.pow(1024, i)); let num = (size / Math.pow(1024, i));
let round = Math.round(num); const round = Math.round(num);
num = round < 10 ? num.toFixed(2) : round < 100 ? num.toFixed(1) : round; num = round < 10 ? num.toFixed(2) : round < 100 ? num.toFixed(1) : round;
return `${num} ${"KMGTPEZY"[i-1]}B`; return `${num} ${"KMGTPEZY"[i - 1]}B`;
} }
export function getTreeWithParents(tree) { export function getTreeWithParents(tree) {
@ -437,14 +435,14 @@ export function getTreeWithSearch(tree, s) {
export function getExtFromPath(path) { export function getExtFromPath(path) {
const filename = path.split("/").pop(); const filename = path.split("/").pop();
if (filename.includes(".")) { if (filename.includes(".")) {
return filename.split('.').pop().toLowerCase(); return filename.split(".").pop().toLowerCase();
} else { } else {
return ""; return "";
} }
} }
export function getExtFromFile(file) { export function getExtFromFile(file) {
const res = file.title.split('.')[1]; const res = file.title.split(".")[1];
if (res === undefined) { if (res === undefined) {
return ""; return "";
} else { } else {
@ -472,7 +470,7 @@ export function getCollectedTime(filename) {
} }
const time = tokens[0].slice(0, -3); const time = tokens[0].slice(0, -3);
const m = new moment(time, 'YYYYMMDD_HH:mm:ss'); const m = new moment(time, "YYYYMMDD_HH:mm:ss");
return m.format(); return m.format();
} }
@ -496,14 +494,14 @@ export function getSubject(filename) {
} }
export function submitStoreEdit(storeObj) { export function submitStoreEdit(storeObj) {
let store = deepCopy(storeObj); const store = deepCopy(storeObj);
store.fileTree = undefined; store.fileTree = undefined;
StoreBackend.updateStore(storeObj.owner, storeObj.name, store) StoreBackend.updateStore(storeObj.owner, storeObj.name, store)
.then((res) => { .then((res) => {
if (res) { if (res) {
showMessage("success", `Successfully saved`); showMessage("success", "Successfully saved");
} else { } else {
showMessage("error", `failed to save: server side failure`); showMessage("error", "failed to save: server side failure");
} }
}) })
.catch(error => { .catch(error => {

View File

@ -1,8 +1,8 @@
import React from 'react'; import React from "react";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
class SigninPage extends React.Component { class SigninPage extends React.Component {
componentDidMount(){ componentDidMount() {
window.location.replace(Setting.getSigninUrl()); window.location.replace(Setting.getSigninUrl());
} }

View File

@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import {Button, Card, Col, Input, Row} from 'antd'; import {Button, Card, Col, Input, Row} from "antd";
import {LinkOutlined} from "@ant-design/icons"; import {LinkOutlined} from "@ant-design/icons";
import * as StoreBackend from "./backend/StoreBackend"; import * as StoreBackend from "./backend/StoreBackend";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
@ -40,7 +40,7 @@ class StoreEditPage extends React.Component {
updateStoreField(key, value) { updateStoreField(key, value) {
value = this.parseStoreField(key, value); value = this.parseStoreField(key, value);
let store = this.state.store; const store = this.state.store;
store[key] = value; store[key] = value;
this.setState({ this.setState({
store: store, store: store,
@ -54,49 +54,49 @@ class StoreEditPage extends React.Component {
{i18next.t("store:Edit Store")}&nbsp;&nbsp;&nbsp;&nbsp; {i18next.t("store:Edit Store")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button type="primary" onClick={this.submitStoreEdit.bind(this)}>{i18next.t("general:Save")}</Button> <Button type="primary" onClick={this.submitStoreEdit.bind(this)}>{i18next.t("general:Save")}</Button>
</div> </div>
} style={{marginLeft: '5px'}} type="inner"> } style={{marginLeft: "5px"}} type="inner">
<Row style={{marginTop: '10px'}} > <Row style={{marginTop: "10px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{i18next.t("general:Name")}: {i18next.t("general:Name")}:
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.store.name} onChange={e => { <Input value={this.state.store.name} onChange={e => {
this.updateStoreField('name', e.target.value); this.updateStoreField("name", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{i18next.t("general:Display name")}: {i18next.t("general:Display name")}:
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.store.displayName} onChange={e => { <Input value={this.state.store.displayName} onChange={e => {
this.updateStoreField('displayName', e.target.value); this.updateStoreField("displayName", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{i18next.t("store:Bucket")}: {i18next.t("store:Bucket")}:
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.store.bucket} onChange={e => { <Input value={this.state.store.bucket} onChange={e => {
this.updateStoreField('bucket', e.target.value); this.updateStoreField("bucket", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{i18next.t("store:Domain")}: {i18next.t("store:Domain")}:
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input prefix={<LinkOutlined/>} value={this.state.store.domain} onChange={e => { <Input prefix={<LinkOutlined />} value={this.state.store.domain} onChange={e => {
this.updateStoreField('domain', e.target.value); this.updateStoreField("domain", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{i18next.t("store:File tree")}: {i18next.t("store:File tree")}:
</Col> </Col>
<Col span={22} > <Col span={22} >
@ -109,23 +109,23 @@ class StoreEditPage extends React.Component {
</Col> </Col>
</Row> </Row>
</Card> </Card>
) );
} }
submitStoreEdit() { submitStoreEdit() {
let store = Setting.deepCopy(this.state.store); const store = Setting.deepCopy(this.state.store);
store.fileTree = undefined; store.fileTree = undefined;
StoreBackend.updateStore(this.state.store.owner, this.state.storeName, store) StoreBackend.updateStore(this.state.store.owner, this.state.storeName, store)
.then((res) => { .then((res) => {
if (res) { if (res) {
Setting.showMessage("success", `Successfully saved`); Setting.showMessage("success", "Successfully saved");
this.setState({ this.setState({
storeName: this.state.store.name, storeName: this.state.store.name,
}); });
this.props.history.push(`/stores/${this.state.store.owner}/${this.state.store.name}`); this.props.history.push(`/stores/${this.state.store.owner}/${this.state.store.name}`);
} else { } else {
Setting.showMessage("error", `failed to save: server side failure`); Setting.showMessage("error", "failed to save: server side failure");
this.updateStoreField('name', this.state.storeName); this.updateStoreField("name", this.state.storeName);
} }
}) })
.catch(error => { .catch(error => {

View File

@ -1,6 +1,6 @@
import React from "react"; import React from "react";
import {Link} from "react-router-dom"; import {Link} from "react-router-dom";
import {Button, Col, Popconfirm, Row, Table} from 'antd'; import {Button, Col, Popconfirm, Row, Table} from "antd";
import moment from "moment"; import moment from "moment";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import * as StoreBackend from "./backend/StoreBackend"; import * as StoreBackend from "./backend/StoreBackend";
@ -37,14 +37,14 @@ class StoreListPage extends React.Component {
bucket: "bucket_name", bucket: "bucket_name",
domain: "https://cdn.example.com", domain: "https://cdn.example.com",
propertiesMap: {}, propertiesMap: {},
} };
} }
addStore() { addStore() {
const newStore = this.newStore(); const newStore = this.newStore();
StoreBackend.addStore(newStore) StoreBackend.addStore(newStore)
.then((res) => { .then((res) => {
Setting.showMessage("success", `Store added successfully`); Setting.showMessage("success", "Store added successfully");
this.setState({ this.setState({
stores: Setting.prependRow(this.state.stores, newStore), stores: Setting.prependRow(this.state.stores, newStore),
}); });
@ -57,7 +57,7 @@ class StoreListPage extends React.Component {
deleteStore(i) { deleteStore(i) {
StoreBackend.deleteStore(this.state.stores[i]) StoreBackend.deleteStore(this.state.stores[i])
.then((res) => { .then((res) => {
Setting.showMessage("success", `Store deleted successfully`); Setting.showMessage("success", "Store deleted successfully");
this.setState({ this.setState({
stores: Setting.deleteRow(this.state.stores, i), stores: Setting.deleteRow(this.state.stores, i),
}); });
@ -71,69 +71,69 @@ class StoreListPage extends React.Component {
const columns = [ const columns = [
{ {
title: i18next.t("general:Name"), title: i18next.t("general:Name"),
dataIndex: 'name', dataIndex: "name",
key: 'name', key: "name",
width: '300px', width: "300px",
sorter: (a, b) => a.name.localeCompare(b.name), sorter: (a, b) => a.name.localeCompare(b.name),
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Link to={`/stores/${record.owner}/${text}/view`}> <Link to={`/stores/${record.owner}/${text}/view`}>
{text} {text}
</Link> </Link>
) );
} },
}, },
{ {
title: i18next.t("general:Display name"), title: i18next.t("general:Display name"),
dataIndex: 'displayName', dataIndex: "displayName",
key: 'displayName', key: "displayName",
// width: '200px', // width: '200px',
sorter: (a, b) => a.displayName.localeCompare(b.displayName), sorter: (a, b) => a.displayName.localeCompare(b.displayName),
}, },
{ {
title: i18next.t("general:Action"), title: i18next.t("general:Action"),
dataIndex: 'action', dataIndex: "action",
key: 'action', key: "action",
width: '240px', width: "240px",
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<div> <div>
<Button style={{marginTop: '10px', marginBottom: '10px', marginRight: '10px'}} onClick={() => this.props.history.push(`/stores/${record.owner}/${record.name}/view`)}>{i18next.t("general:View")}</Button> <Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} onClick={() => this.props.history.push(`/stores/${record.owner}/${record.name}/view`)}>{i18next.t("general:View")}</Button>
{ {
!Setting.isLocalAdminUser(this.props.account) ? null : ( !Setting.isLocalAdminUser(this.props.account) ? null : (
<React.Fragment> <React.Fragment>
<Button style={{marginBottom: '10px', marginRight: '10px'}} type="primary" onClick={() => this.props.history.push(`/stores/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button> <Button style={{marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/stores/${record.owner}/${record.name}`)}>{i18next.t("general:Edit")}</Button>
<Popconfirm <Popconfirm
title={`Sure to delete store: ${record.name} ?`} title={`Sure to delete store: ${record.name} ?`}
onConfirm={() => this.deleteStore(index)} onConfirm={() => this.deleteStore(index)}
okText="OK" okText="OK"
cancelText="Cancel" cancelText="Cancel"
> >
<Button style={{marginBottom: '10px'}} type="danger">{i18next.t("general:Delete")}</Button> <Button style={{marginBottom: "10px"}} type="danger">{i18next.t("general:Delete")}</Button>
</Popconfirm> </Popconfirm>
</React.Fragment> </React.Fragment>
) )
} }
</div> </div>
) );
} },
}, },
]; ];
return ( return (
<div> <div>
<Table columns={columns} dataSource={stores} rowKey="name" size="middle" bordered pagination={{pageSize: 100}} <Table columns={columns} dataSource={stores} rowKey="name" size="middle" bordered pagination={{pageSize: 100}}
title={() => ( title={() => (
<div> <div>
{i18next.t("general:Stores")}&nbsp;&nbsp;&nbsp;&nbsp; {i18next.t("general:Stores")}&nbsp;&nbsp;&nbsp;&nbsp;
{ {
!Setting.isLocalAdminUser(this.props.account) ? null : ( !Setting.isLocalAdminUser(this.props.account) ? null : (
<Button type="primary" size="small" onClick={this.addStore.bind(this)}>{i18next.t("general:Add")}</Button> <Button type="primary" size="small" onClick={this.addStore.bind(this)}>{i18next.t("general:Add")}</Button>
) )
} }
</div> </div>
)} )}
loading={stores === null} loading={stores === null}
/> />
</div> </div>
); );

View File

@ -1,6 +1,6 @@
import React from "react"; import React from "react";
import {DownOutlined, DeleteOutlined, UpOutlined} from '@ant-design/icons'; import {DeleteOutlined, DownOutlined, UpOutlined} from "@ant-design/icons";
import {Button, Col, Input, Row, Table, Tooltip} from 'antd'; import {Button, Col, Input, Row, Table, Tooltip} from "antd";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import i18next from "i18next"; import i18next from "i18next";
@ -36,7 +36,7 @@ class VectorTable extends React.Component {
} }
addRow(table) { addRow(table) {
let row = {no: table.length, name: `New Vector - ${table.length}`, data: []}; const row = {no: table.length, name: `New Vector - ${table.length}`, data: []};
if (table === undefined) { if (table === undefined) {
table = []; table = [];
} }
@ -63,43 +63,43 @@ class VectorTable extends React.Component {
const columns = [ const columns = [
{ {
title: i18next.t("general:No."), title: i18next.t("general:No."),
dataIndex: 'no', dataIndex: "no",
key: 'no', key: "no",
width: '60px', width: "60px",
render: (text, record, index) => { render: (text, record, index) => {
return (index + 1); return (index + 1);
} },
}, },
{ {
title: i18next.t("general:Name"), title: i18next.t("general:Name"),
dataIndex: 'name', dataIndex: "name",
key: 'name', key: "name",
width: '200px', width: "200px",
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Input value={text} onChange={e => { <Input value={text} onChange={e => {
this.updateField(table, index, 'name', e.target.value); this.updateField(table, index, "name", e.target.value);
}} /> }} />
) );
} },
}, },
{ {
title: i18next.t("general:Data"), title: i18next.t("general:Data"),
dataIndex: 'data', dataIndex: "data",
key: 'data', key: "data",
// width: '300px', // width: '300px',
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Input value={text} onChange={e => { <Input value={text} onChange={e => {
this.updateField(table, index, 'data', e.target.value); this.updateField(table, index, "data", e.target.value);
}} /> }} />
) );
} },
}, },
{ {
title: i18next.t("general:Action"), title: i18next.t("general:Action"),
key: 'action', key: "action",
width: '100px', width: "100px",
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<div> <div>
@ -114,23 +114,23 @@ class VectorTable extends React.Component {
</Tooltip> </Tooltip>
</div> </div>
); );
} },
}, },
]; ];
return ( return (
<Table rowKey="index" columns={columns} dataSource={table} size="middle" bordered pagination={false} <Table rowKey="index" columns={columns} dataSource={table} size="middle" bordered pagination={false}
title={() => ( title={() => (
<div> <div>
{this.props.title}&nbsp;&nbsp;&nbsp;&nbsp; {this.props.title}&nbsp;&nbsp;&nbsp;&nbsp;
<Button style={{marginRight: "5px"}} type="primary" size="small" onClick={() => this.addRow(table)}>{i18next.t("general:Add")}</Button> <Button style={{marginRight: "5px"}} type="primary" size="small" onClick={() => this.addRow(table)}>{i18next.t("general:Add")}</Button>
{ {
this.props.wordset === undefined ? null : ( this.props.wordset === undefined ? null : (
<Button style={{marginLeft: "5px", marginRight: "5px"}} size="small" onClick={() => Setting.downloadXlsx(this.props.wordset)}>{i18next.t("general:Download")}</Button> <Button style={{marginLeft: "5px", marginRight: "5px"}} size="small" onClick={() => Setting.downloadXlsx(this.props.wordset)}>{i18next.t("general:Download")}</Button>
) )
} }
</div> </div>
)} )}
/> />
); );
} }
@ -138,7 +138,7 @@ class VectorTable extends React.Component {
render() { render() {
return ( return (
<div> <div>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col span={24}> <Col span={24}>
{ {
this.renderTable(this.props.table) this.renderTable(this.props.table)
@ -146,7 +146,7 @@ class VectorTable extends React.Component {
</Col> </Col>
</Row> </Row>
</div> </div>
) );
} }
} }

View File

@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import {Button, Card, Col, Input, InputNumber, Row} from 'antd'; import {Button, Card, Col, Input, InputNumber, Row} from "antd";
import * as VectorsetBackend from "./backend/VectorsetBackend"; import * as VectorsetBackend from "./backend/VectorsetBackend";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import i18next from "i18next"; import i18next from "i18next";
@ -39,7 +39,7 @@ class VectorsetEditPage extends React.Component {
updateVectorsetField(key, value) { updateVectorsetField(key, value) {
value = this.parseVectorsetField(key, value); value = this.parseVectorsetField(key, value);
let vectorset = this.state.vectorset; const vectorset = this.state.vectorset;
vectorset[key] = value; vectorset[key] = value;
this.setState({ this.setState({
vectorset: vectorset, vectorset: vectorset,
@ -53,106 +53,106 @@ class VectorsetEditPage extends React.Component {
{i18next.t("vectorset:Edit Vectorset")}&nbsp;&nbsp;&nbsp;&nbsp; {i18next.t("vectorset:Edit Vectorset")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button type="primary" onClick={this.submitVectorsetEdit.bind(this)}>{i18next.t("general:Save")}</Button> <Button type="primary" onClick={this.submitVectorsetEdit.bind(this)}>{i18next.t("general:Save")}</Button>
</div> </div>
} style={{marginLeft: '5px'}} type="inner"> } style={{marginLeft: "5px"}} type="inner">
<Row style={{marginTop: '10px'}} > <Row style={{marginTop: "10px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{i18next.t("general:Name")}: {i18next.t("general:Name")}:
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.vectorset.name} onChange={e => { <Input value={this.state.vectorset.name} onChange={e => {
this.updateVectorsetField('name', e.target.value); this.updateVectorsetField("name", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{i18next.t("general:Display name")}: {i18next.t("general:Display name")}:
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.vectorset.displayName} onChange={e => { <Input value={this.state.vectorset.displayName} onChange={e => {
this.updateVectorsetField('displayName', e.target.value); this.updateVectorsetField("displayName", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{i18next.t("general:URL")}: {i18next.t("general:URL")}:
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input prefix={<LinkOutlined/>} value={this.state.vectorset.url} onChange={e => { <Input prefix={<LinkOutlined />} value={this.state.vectorset.url} onChange={e => {
this.updateVectorsetField('url', e.target.value); this.updateVectorsetField("url", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{i18next.t("vectorset:File name")}: {i18next.t("vectorset:File name")}:
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.vectorset.fileName} onChange={e => { <Input value={this.state.vectorset.fileName} onChange={e => {
this.updateVectorsetField('fileName', e.target.value); this.updateVectorsetField("fileName", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{i18next.t("vectorset:File size")}: {i18next.t("vectorset:File size")}:
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.vectorset.fileSize} onChange={e => { <Input value={this.state.vectorset.fileSize} onChange={e => {
this.updateVectorsetField('fileSize', e.target.value); this.updateVectorsetField("fileSize", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{i18next.t("vectorset:Dimension")}: {i18next.t("vectorset:Dimension")}:
</Col> </Col>
<Col span={22} > <Col span={22} >
<InputNumber value={this.state.vectorset.dimension} onChange={value => { <InputNumber value={this.state.vectorset.dimension} onChange={value => {
this.updateVectorsetField('dimension', value); this.updateVectorsetField("dimension", value);
}} /> }} />
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{i18next.t("vectorset:Count")}: {i18next.t("vectorset:Count")}:
</Col> </Col>
<Col span={22} > <Col span={22} >
<InputNumber value={this.state.vectorset.count} onChange={value => { <InputNumber value={this.state.vectorset.count} onChange={value => {
this.updateVectorsetField('count', value); this.updateVectorsetField("count", value);
}} /> }} />
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{i18next.t("vectorset:Example vectors")}: {i18next.t("vectorset:Example vectors")}:
</Col> </Col>
<Col span={22} > <Col span={22} >
<VectorTable <VectorTable
title={i18next.t("vectorset:Example vectors")} title={i18next.t("vectorset:Example vectors")}
table={this.state.vectorset.vectors} table={this.state.vectorset.vectors}
onUpdateTable={(value) => { this.updateVectorsetField('vectors', value)}} onUpdateTable={(value) => {this.updateVectorsetField("vectors", value);}}
/> />
</Col> </Col>
</Row> </Row>
</Card> </Card>
) );
} }
submitVectorsetEdit() { submitVectorsetEdit() {
let vectorset = Setting.deepCopy(this.state.vectorset); const vectorset = Setting.deepCopy(this.state.vectorset);
VectorsetBackend.updateVectorset(this.state.vectorset.owner, this.state.vectorsetName, vectorset) VectorsetBackend.updateVectorset(this.state.vectorset.owner, this.state.vectorsetName, vectorset)
.then((res) => { .then((res) => {
if (res) { if (res) {
Setting.showMessage("success", `Successfully saved`); Setting.showMessage("success", "Successfully saved");
this.setState({ this.setState({
vectorsetName: this.state.vectorset.name, vectorsetName: this.state.vectorset.name,
}); });
this.props.history.push(`/vectorsets/${this.state.vectorset.name}`); this.props.history.push(`/vectorsets/${this.state.vectorset.name}`);
} else { } else {
Setting.showMessage("error", `failed to save: server side failure`); Setting.showMessage("error", "failed to save: server side failure");
this.updateVectorsetField('name', this.state.vectorsetName); this.updateVectorsetField("name", this.state.vectorsetName);
} }
}) })
.catch(error => { .catch(error => {

View File

@ -1,6 +1,6 @@
import React from "react"; import React from "react";
import {Link} from "react-router-dom"; import {Link} from "react-router-dom";
import {Button, Col, Popconfirm, Row, Table} from 'antd'; import {Button, Col, Popconfirm, Row, Table} from "antd";
import moment from "moment"; import moment from "moment";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import * as VectorsetBackend from "./backend/VectorsetBackend"; import * as VectorsetBackend from "./backend/VectorsetBackend";
@ -40,14 +40,14 @@ class VectorsetListPage extends React.Component {
dimension: 128, dimension: 128,
count: 10000, count: 10000,
vectors: [], vectors: [],
} };
} }
addVectorset() { addVectorset() {
const newVectorset = this.newVectorset(); const newVectorset = this.newVectorset();
VectorsetBackend.addVectorset(newVectorset) VectorsetBackend.addVectorset(newVectorset)
.then((res) => { .then((res) => {
Setting.showMessage("success", `Vectorset added successfully`); Setting.showMessage("success", "Vectorset added successfully");
this.setState({ this.setState({
vectorsets: Setting.prependRow(this.state.vectorsets, newVectorset), vectorsets: Setting.prependRow(this.state.vectorsets, newVectorset),
}); });
@ -60,7 +60,7 @@ class VectorsetListPage extends React.Component {
deleteVectorset(i) { deleteVectorset(i) {
VectorsetBackend.deleteVectorset(this.state.vectorsets[i]) VectorsetBackend.deleteVectorset(this.state.vectorsets[i])
.then((res) => { .then((res) => {
Setting.showMessage("success", `Vectorset deleted successfully`); Setting.showMessage("success", "Vectorset deleted successfully");
this.setState({ this.setState({
vectorsets: Setting.deleteRow(this.state.vectorsets, i), vectorsets: Setting.deleteRow(this.state.vectorsets, i),
}); });
@ -74,30 +74,30 @@ class VectorsetListPage extends React.Component {
const columns = [ const columns = [
{ {
title: i18next.t("general:Name"), title: i18next.t("general:Name"),
dataIndex: 'name', dataIndex: "name",
key: 'name', key: "name",
width: '140px', width: "140px",
sorter: (a, b) => a.name.localeCompare(b.name), sorter: (a, b) => a.name.localeCompare(b.name),
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Link to={`/vectorsets/${text}`}> <Link to={`/vectorsets/${text}`}>
{text} {text}
</Link> </Link>
) );
} },
}, },
{ {
title: i18next.t("general:Display name"), title: i18next.t("general:Display name"),
dataIndex: 'displayName', dataIndex: "displayName",
key: 'displayName', key: "displayName",
width: '200px', width: "200px",
sorter: (a, b) => a.displayName.localeCompare(b.displayName), sorter: (a, b) => a.displayName.localeCompare(b.displayName),
}, },
{ {
title: i18next.t("general:URL"), title: i18next.t("general:URL"),
dataIndex: 'url', dataIndex: "url",
key: 'url', key: "url",
width: '250px', width: "250px",
sorter: (a, b) => a.url.localeCompare(b.url), sorter: (a, b) => a.url.localeCompare(b.url),
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
@ -106,80 +106,80 @@ class VectorsetListPage extends React.Component {
Setting.getShortText(text) Setting.getShortText(text)
} }
</a> </a>
) );
} },
}, },
{ {
title: i18next.t("vectorset:File name"), title: i18next.t("vectorset:File name"),
dataIndex: 'fileName', dataIndex: "fileName",
key: 'fileName', key: "fileName",
width: '200px', width: "200px",
sorter: (a, b) => a.fileName.localeCompare(b.fileName), sorter: (a, b) => a.fileName.localeCompare(b.fileName),
}, },
{ {
title: i18next.t("vectorset:File size"), title: i18next.t("vectorset:File size"),
dataIndex: 'fileSize', dataIndex: "fileSize",
key: 'fileSize', key: "fileSize",
width: '120px', width: "120px",
sorter: (a, b) => a.fileSize.localeCompare(b.fileSize), sorter: (a, b) => a.fileSize.localeCompare(b.fileSize),
}, },
{ {
title: i18next.t("vectorset:Dimension"), title: i18next.t("vectorset:Dimension"),
dataIndex: 'dimension', dataIndex: "dimension",
key: 'dimension', key: "dimension",
width: '110px', width: "110px",
sorter: (a, b) => a.dimension - b.dimension, sorter: (a, b) => a.dimension - b.dimension,
}, },
{ {
title: i18next.t("vectorset:Example vectors"), title: i18next.t("vectorset:Example vectors"),
dataIndex: 'vectors', dataIndex: "vectors",
key: 'vectors', key: "vectors",
// width: '120px', // width: '120px',
sorter: (a, b) => a.vectors.localeCompare(b.vectors), sorter: (a, b) => a.vectors.localeCompare(b.vectors),
render: (text, record, index) => { render: (text, record, index) => {
return Setting.getTags(text); return Setting.getTags(text);
} },
}, },
{ {
title: i18next.t("vectorset:Count"), title: i18next.t("vectorset:Count"),
dataIndex: 'count', dataIndex: "count",
key: 'count', key: "count",
width: '110px', width: "110px",
sorter: (a, b) => a.count - b.count, sorter: (a, b) => a.count - b.count,
}, },
{ {
title: i18next.t("general:Action"), title: i18next.t("general:Action"),
dataIndex: 'action', dataIndex: "action",
key: 'action', key: "action",
width: '80px', width: "80px",
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<div> <div>
<Button style={{marginTop: '10px', marginBottom: '10px', marginRight: '10px'}} type="primary" onClick={() => this.props.history.push(`/vectorsets/${record.name}`)}>{i18next.t("general:Edit")}</Button> <Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/vectorsets/${record.name}`)}>{i18next.t("general:Edit")}</Button>
<Popconfirm <Popconfirm
title={`Sure to delete vectorset: ${record.name} ?`} title={`Sure to delete vectorset: ${record.name} ?`}
onConfirm={() => this.deleteVectorset(index)} onConfirm={() => this.deleteVectorset(index)}
okText="OK" okText="OK"
cancelText="Cancel" cancelText="Cancel"
> >
<Button style={{marginBottom: '10px'}} type="danger">{i18next.t("general:Delete")}</Button> <Button style={{marginBottom: "10px"}} type="danger">{i18next.t("general:Delete")}</Button>
</Popconfirm> </Popconfirm>
</div> </div>
) );
} },
}, },
]; ];
return ( return (
<div> <div>
<Table columns={columns} dataSource={vectorsets} rowKey="name" size="middle" bordered pagination={{pageSize: 100}} <Table columns={columns} dataSource={vectorsets} rowKey="name" size="middle" bordered pagination={{pageSize: 100}}
title={() => ( title={() => (
<div> <div>
{i18next.t("general:Vectorsets")}&nbsp;&nbsp;&nbsp;&nbsp; {i18next.t("general:Vectorsets")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button type="primary" size="small" onClick={this.addVectorset.bind(this)}>{i18next.t("general:Add")}</Button> <Button type="primary" size="small" onClick={this.addVectorset.bind(this)}>{i18next.t("general:Add")}</Button>
</div> </div>
)} )}
loading={vectorsets === null} loading={vectorsets === null}
/> />
</div> </div>
); );

View File

@ -1,7 +1,7 @@
import React from "react"; import React from "react";
import Player from 'aliplayer-react'; import Player from "aliplayer-react";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import BulletScreen from 'rc-bullets'; import BulletScreen from "rc-bullets";
class Video extends React.Component { class Video extends React.Component {
constructor(props) { constructor(props) {
@ -62,8 +62,8 @@ class Video extends React.Component {
backgroundColor: "rgb(255,255,255)", backgroundColor: "rgb(255,255,255)",
}, { }, {
onStart: (bulletId, screen) => { onStart: (bulletId, screen) => {
let bulletIdTextMap = this.state.bulletIdTextMap; const bulletIdTextMap = this.state.bulletIdTextMap;
let bulletTextMap = this.state.bulletTextMap; const bulletTextMap = this.state.bulletTextMap;
bulletIdTextMap[bulletId] = label.text; bulletIdTextMap[bulletId] = label.text;
bulletTextMap[label.text] = 1; bulletTextMap[label.text] = 1;
@ -76,8 +76,8 @@ class Video extends React.Component {
// console.log(`start: ${bulletId}`); // console.log(`start: ${bulletId}`);
}, },
onEnd: (bulletId, screen) => { onEnd: (bulletId, screen) => {
let bulletIdTextMap = this.state.bulletIdTextMap; const bulletIdTextMap = this.state.bulletIdTextMap;
let bulletTextMap = this.state.bulletTextMap; const bulletTextMap = this.state.bulletTextMap;
const text = bulletIdTextMap[bulletId]; const text = bulletIdTextMap[bulletId];
delete bulletIdTextMap[bulletId]; delete bulletIdTextMap[bulletId];
@ -140,11 +140,11 @@ class Video extends React.Component {
initPlayer(player) { initPlayer(player) {
// https://help.aliyun.com/document_detail/125572.html // https://help.aliyun.com/document_detail/125572.html
// https://github.com/zerosoul/rc-bullets // https://github.com/zerosoul/rc-bullets
player.on('ready', () => {this.handleReady(player)}); player.on("ready", () => {this.handleReady(player);});
player.on('timeupdate', () => {this.onTimeUpdate(player)}); player.on("timeupdate", () => {this.onTimeUpdate(player);});
player.on('play', () => {this.onPlay()}); player.on("play", () => {this.onPlay();});
player.on('pause', () => {this.onPause()}); player.on("pause", () => {this.onPause();});
player.on('completeSeek', () => {this.state.screen.clear()}); player.on("completeSeek", () => {this.state.screen.clear();});
} }
render() { render() {
@ -186,7 +186,7 @@ class Video extends React.Component {
}} }}
/> />
</div> </div>
) );
} }
} }

View File

@ -1,5 +1,5 @@
import React, { Component } from "react"; import React, {Component} from "react";
import ReactEcharts from 'echarts-for-react'; import ReactEcharts from "echarts-for-react";
class VideoDataChart extends Component { class VideoDataChart extends Component {
drawPic(data, currentTime, height) { drawPic(data, currentTime, height) {
@ -24,35 +24,35 @@ class VideoDataChart extends Component {
const option = { const option = {
grid: { grid: {
top: '5%', top: "5%",
left: '3%', left: "3%",
right: '4%', right: "4%",
bottom: '8%', bottom: "8%",
containLabel: true containLabel: true,
}, },
xAxis: { xAxis: {
type: 'category', type: "category",
data: xAxisData data: xAxisData,
}, },
yAxis: { yAxis: {
type: 'value', type: "value",
min: dataMin, min: dataMin,
max: dataMax max: dataMax,
}, },
series: [ series: [
{ {
name: 'Data', name: "Data",
type: 'line', type: "line",
data: seriesData, data: seriesData,
markLine: { markLine: {
data: [ data: [
[ [
{ {
symbol: 'none', symbol: "none",
x: `${currentTime / 0.65 + 40}`, x: `${currentTime / 0.65 + 40}`,
y: '80%', y: "80%",
lineStyle: { lineStyle: {
color: 'red' color: "red",
}, },
// label: { // label: {
// position: 'start', // position: 'start',
@ -60,19 +60,19 @@ class VideoDataChart extends Component {
// }, // },
}, },
{ {
symbol: 'none', symbol: "none",
x: `${currentTime / 0.65 + 40}`, x: `${currentTime / 0.65 + 40}`,
y: '0%', y: "0%",
// label: { // label: {
// position: 'start', // position: 'start',
// formatter: 'Max' // formatter: 'Max'
// }, // },
} },
], ],
] ],
} },
} },
] ],
}; };
return ( return (

View File

@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import {Affix, Button, Card, Col, Input, Row, Select, Switch} from 'antd'; import {Affix, Button, Card, Col, Input, Row, Select, Switch} from "antd";
import * as VideoBackend from "./backend/VideoBackend"; import * as VideoBackend from "./backend/VideoBackend";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import i18next from "i18next"; import i18next from "i18next";
@ -9,7 +9,7 @@ import LabelTable from "./LabelTable";
import * as Papa from "papaparse"; import * as Papa from "papaparse";
import VideoDataChart from "./VideoDataChart"; import VideoDataChart from "./VideoDataChart";
const { Option } = Select; const {Option} = Select;
class VideoEditPage extends React.Component { class VideoEditPage extends React.Component {
constructor(props) { constructor(props) {
@ -55,7 +55,7 @@ class VideoEditPage extends React.Component {
updateVideoField(key, value) { updateVideoField(key, value) {
value = this.parseVideoField(key, value); value = this.parseVideoField(key, value);
let video = this.state.video; const video = this.state.video;
video[key] = value; video[key] = value;
this.setState({ this.setState({
video: video, video: video,
@ -73,7 +73,7 @@ class VideoEditPage extends React.Component {
return null; return null;
} }
let task = {}; const task = {};
task.video = { task.video = {
vid: this.state.video.videoId, vid: this.state.video.videoId,
playAuth: this.state.video.playAuth, playAuth: this.state.video.playAuth,
@ -98,16 +98,16 @@ class VideoEditPage extends React.Component {
this.state.currentTime this.state.currentTime
} }
</div> </div>
<div className="screen" style={{position: "absolute", zIndex: 100, pointerEvents: "none", width: '440px', height: '472px', marginLeft: '200px', marginRight: '200px', backgroundColor: "rgba(255,0,0,0)" }}></div> <div className="screen" style={{position: "absolute", zIndex: 100, pointerEvents: "none", width: "440px", height: "472px", marginLeft: "200px", marginRight: "200px", backgroundColor: "rgba(255,0,0,0)"}}></div>
<Video task={task} labels={this.state.video.labels} <Video task={task} labels={this.state.video.labels}
onUpdateTime={(time) => {this.setState({currentTime: time})}} onUpdateTime={(time) => {this.setState({currentTime: time});}}
onCreatePlayer={(player) => {this.setState({player: player})}} onCreatePlayer={(player) => {this.setState({player: player});}}
onCreateScreen={(screen) => {this.setState({screen: screen})}} onCreateScreen={(screen) => {this.setState({screen: screen});}}
onCreateVideo={(videoObj) => {this.setState({videoObj: videoObj})}} onCreateVideo={(videoObj) => {this.setState({videoObj: videoObj});}}
onPause={() => {this.onPause()}} onPause={() => {this.onPause();}}
/> />
</div> </div>
) );
} }
getDataAndParse(dataUrl) { getDataAndParse(dataUrl) {
@ -115,11 +115,11 @@ class VideoEditPage extends React.Component {
method: "GET", method: "GET",
}).then(res => res.text()) }).then(res => res.text())
.then(res => { .then(res => {
const result = Papa.parse(res, { header: true }); const result = Papa.parse(res, {header: true});
let data = result.data; let data = result.data;
data = data.filter(item => item.time !== ""); data = data.filter(item => item.time !== "");
data = data.map(item => { data = data.map(item => {
let res = {}; const res = {};
res.time = Number(item.time); res.time = Number(item.time);
res.data = Number(item.data); res.data = Number(item.data);
return res; return res;
@ -127,7 +127,7 @@ class VideoEditPage extends React.Component {
this.setState({ this.setState({
videoData: data, videoData: data,
}); });
}); });
} }
renderVideo() { renderVideo() {
@ -137,76 +137,76 @@ class VideoEditPage extends React.Component {
{i18next.t("video:Edit Video")}&nbsp;&nbsp;&nbsp;&nbsp; {i18next.t("video:Edit Video")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button type="primary" onClick={this.submitVideoEdit.bind(this)}>{i18next.t("general:Save")}</Button> <Button type="primary" onClick={this.submitVideoEdit.bind(this)}>{i18next.t("general:Save")}</Button>
</div> </div>
} style={{marginLeft: '5px'}} type="inner"> } style={{marginLeft: "5px"}} type="inner">
<Row style={{marginTop: '10px'}} > <Row style={{marginTop: "10px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{i18next.t("general:Name")}: {i18next.t("general:Name")}:
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.video.name} onChange={e => { <Input value={this.state.video.name} onChange={e => {
this.updateVideoField('name', e.target.value); this.updateVideoField("name", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{i18next.t("general:Display name")}: {i18next.t("general:Display name")}:
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.video.displayName} onChange={e => { <Input value={this.state.video.displayName} onChange={e => {
this.updateVideoField('displayName', e.target.value); this.updateVideoField("displayName", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{i18next.t("video:Video ID")}: {i18next.t("video:Video ID")}:
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input disabled={true} value={this.state.video.videoId} onChange={e => { <Input disabled={true} value={this.state.video.videoId} onChange={e => {
this.updateVideoField('videoId', e.target.value); this.updateVideoField("videoId", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{i18next.t("video:Cover")}: {i18next.t("video:Cover")}:
</Col> </Col>
<Col span={22} style={(Setting.isMobile()) ? {maxWidth:'100%'} :{}}> <Col span={22} style={(Setting.isMobile()) ? {maxWidth: "100%"} : {}}>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 1}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 1}>
{i18next.t("general:URL")} : {i18next.t("general:URL")} :
</Col> </Col>
<Col span={23} > <Col span={23} >
<Input disabled={true} prefix={<LinkOutlined/>} value={this.state.video.coverUrl} onChange={e => { <Input disabled={true} prefix={<LinkOutlined />} value={this.state.video.coverUrl} onChange={e => {
this.updateVideoField('coverUrl', e.target.value); this.updateVideoField("coverUrl", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 1}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 1}>
{i18next.t("general:Preview")}: {i18next.t("general:Preview")}:
</Col> </Col>
<Col span={23} > <Col span={23} >
<a target="_blank" rel="noreferrer" href={this.state.video.coverUrl}> <a target="_blank" rel="noreferrer" href={this.state.video.coverUrl}>
<img src={this.state.video.coverUrl} alt={this.state.video.coverUrl} height={90} style={{marginBottom: '20px'}}/> <img src={this.state.video.coverUrl} alt={this.state.video.coverUrl} height={90} style={{marginBottom: "20px"}} />
</a> </a>
</Col> </Col>
</Row> </Row>
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{i18next.t("video:Tag on pause")}: {i18next.t("video:Tag on pause")}:
</Col> </Col>
<Col span={22} > <Col span={22} >
<Switch checked={this.state.video.tagOnPause} onChange={checked => { <Switch checked={this.state.video.tagOnPause} onChange={checked => {
this.updateVideoField('tagOnPause', checked); this.updateVideoField("tagOnPause", checked);
}} /> }} />
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{i18next.t("video:Video")}: {i18next.t("video:Video")}:
</Col> </Col>
<Col span={11} style={(Setting.isMobile()) ? {maxWidth: "100%"} : {}}> <Col span={11} style={(Setting.isMobile()) ? {maxWidth: "100%"} : {}}>
@ -215,14 +215,14 @@ class VideoEditPage extends React.Component {
{ {
this.state.video !== null ? this.renderVideoContent() : null this.state.video !== null ? this.renderVideoContent() : null
} }
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{i18next.t("general:Data")}: {i18next.t("general:Data")}:
</Col> </Col>
<Col span={22} > <Col span={22} >
<Select virtual={false} style={{width: '100%', marginBottom: "10px"}} value={this.state.video.dataUrl} onChange={(value => { <Select virtual={false} style={{width: "100%", marginBottom: "10px"}} value={this.state.video.dataUrl} onChange={(value => {
this.getDataAndParse(value); this.getDataAndParse(value);
this.updateVideoField('dataUrl', value); this.updateVideoField("dataUrl", value);
})}> })}>
{ {
this.state.video.dataUrls?.map((dataUrl, index) => <Option key={index} value={dataUrl}>{dataUrl.split("/").pop()}</Option>) this.state.video.dataUrls?.map((dataUrl, index) => <Option key={index} value={dataUrl}>{dataUrl.split("/").pop()}</Option>)
@ -253,27 +253,27 @@ class VideoEditPage extends React.Component {
player={this.state.player} player={this.state.player}
screen={this.state.screen} screen={this.state.screen}
videoObj={this.state.videoObj} videoObj={this.state.videoObj}
onUpdateTable={(value) => {this.updateVideoField('labels', value)}} onUpdateTable={(value) => {this.updateVideoField("labels", value);}}
/> />
</Col> </Col>
</Row> </Row>
</Card> </Card>
) );
} }
submitVideoEdit() { submitVideoEdit() {
let video = Setting.deepCopy(this.state.video); const video = Setting.deepCopy(this.state.video);
VideoBackend.updateVideo(this.state.video.owner, this.state.videoName, video) VideoBackend.updateVideo(this.state.video.owner, this.state.videoName, video)
.then((res) => { .then((res) => {
if (res) { if (res) {
Setting.showMessage("success", `Successfully saved`); Setting.showMessage("success", "Successfully saved");
this.setState({ this.setState({
videoName: this.state.video.name, videoName: this.state.video.name,
}); });
this.props.history.push(`/videos/${this.state.video.name}`); this.props.history.push(`/videos/${this.state.video.name}`);
} else { } else {
Setting.showMessage("error", `failed to save: server side failure`); Setting.showMessage("error", "failed to save: server side failure");
this.updateVideoField('name', this.state.videoName); this.updateVideoField("name", this.state.videoName);
} }
}) })
.catch(error => { .catch(error => {

View File

@ -1,6 +1,6 @@
import React from "react"; import React from "react";
import {Link} from "react-router-dom"; import {Link} from "react-router-dom";
import {Button, Col, Popconfirm, Row, Table, Upload} from 'antd'; import {Button, Col, Popconfirm, Row, Table, Upload} from "antd";
import {UploadOutlined} from "@ant-design/icons"; import {UploadOutlined} from "@ant-design/icons";
import moment from "moment"; import moment from "moment";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
@ -41,14 +41,14 @@ class VideoListPage extends React.Component {
dataUrls: [], dataUrls: [],
dataUrl: "", dataUrl: "",
playAuth: "", playAuth: "",
} };
} }
addVideo() { addVideo() {
const newVideo = this.newVideo(); const newVideo = this.newVideo();
VideoBackend.addVideo(newVideo) VideoBackend.addVideo(newVideo)
.then((res) => { .then((res) => {
Setting.showMessage("success", `Video added successfully`); Setting.showMessage("success", "Video added successfully");
this.setState({ this.setState({
videos: Setting.prependRow(this.state.videos, newVideo), videos: Setting.prependRow(this.state.videos, newVideo),
}); });
@ -61,7 +61,7 @@ class VideoListPage extends React.Component {
deleteVideo(i) { deleteVideo(i) {
VideoBackend.deleteVideo(this.state.videos[i]) VideoBackend.deleteVideo(this.state.videos[i])
.then((res) => { .then((res) => {
Setting.showMessage("success", `Video deleted successfully`); Setting.showMessage("success", "Video deleted successfully");
this.setState({ this.setState({
videos: Setting.deleteRow(this.state.videos, i), videos: Setting.deleteRow(this.state.videos, i),
}); });
@ -72,28 +72,28 @@ class VideoListPage extends React.Component {
} }
uploadFile(info) { uploadFile(info) {
const { status, response: res } = info.file; const {status, response: res} = info.file;
if (status !== 'uploading') { if (status !== "uploading") {
console.log(info.file, info.fileList); console.log(info.file, info.fileList);
} }
if (status === 'done') { if (status === "done") {
if (res.status === 'ok') { if (res.status === "ok") {
Setting.showMessage("success", `上传视频成功`); Setting.showMessage("success", "上传视频成功");
const videoName = res.data; const videoName = res.data;
this.props.history.push(`/videos/${videoName}`); this.props.history.push(`/videos/${videoName}`);
} else { } else {
Setting.showMessage("error", `上传视频失败:${res.msg}`); Setting.showMessage("error", `上传视频失败:${res.msg}`);
} }
} else if (status === 'error') { } else if (status === "error") {
Setting.showMessage("success", `上传视频失败`); Setting.showMessage("success", "上传视频失败");
} }
} }
renderUpload() { renderUpload() {
const props = { const props = {
name: 'file', name: "file",
accept: '.mp4', accept: ".mp4",
method: 'post', method: "post",
action: `${Setting.ServerUrl}/api/upload-video`, action: `${Setting.ServerUrl}/api/upload-video`,
withCredentials: true, withCredentials: true,
onChange: (info) => { onChange: (info) => {
@ -107,109 +107,109 @@ class VideoListPage extends React.Component {
<UploadOutlined /> 上传视频.mp4 <UploadOutlined /> 上传视频.mp4
</Button> </Button>
</Upload> </Upload>
) );
} }
renderTable(videos) { renderTable(videos) {
const columns = [ const columns = [
{ {
title: i18next.t("general:Name"), title: i18next.t("general:Name"),
dataIndex: 'name', dataIndex: "name",
key: 'name', key: "name",
width: '140px', width: "140px",
sorter: (a, b) => a.name.localeCompare(b.name), sorter: (a, b) => a.name.localeCompare(b.name),
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Link to={`/videos/${text}`}> <Link to={`/videos/${text}`}>
{text} {text}
</Link> </Link>
) );
} },
}, },
{ {
title: i18next.t("general:Display name"), title: i18next.t("general:Display name"),
dataIndex: 'displayName', dataIndex: "displayName",
key: 'displayName', key: "displayName",
width: '200px', width: "200px",
sorter: (a, b) => a.displayName.localeCompare(b.displayName), sorter: (a, b) => a.displayName.localeCompare(b.displayName),
}, },
{ {
title: i18next.t("video:Video ID"), title: i18next.t("video:Video ID"),
dataIndex: 'videoId', dataIndex: "videoId",
key: 'videoId', key: "videoId",
width: '250px', width: "250px",
sorter: (a, b) => a.videoId.localeCompare(b.videoId), sorter: (a, b) => a.videoId.localeCompare(b.videoId),
}, },
{ {
title: i18next.t("video:Cover"), title: i18next.t("video:Cover"),
dataIndex: 'coverUrl', dataIndex: "coverUrl",
key: 'coverUrl', key: "coverUrl",
width: '200px', width: "200px",
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<a target="_blank" rel="noreferrer" href={text}> <a target="_blank" rel="noreferrer" href={text}>
<img src={text} alt={text} width={150} /> <img src={text} alt={text} width={150} />
</a> </a>
) );
} },
}, },
{ {
title: i18next.t("video:Labels"), title: i18next.t("video:Labels"),
dataIndex: 'labels', dataIndex: "labels",
key: 'labels', key: "labels",
// width: '120px', // width: '120px',
sorter: (a, b) => a.vectors.localeCompare(b.vectors), sorter: (a, b) => a.vectors.localeCompare(b.vectors),
render: (text, record, index) => { render: (text, record, index) => {
return Setting.getLabelTags(text); return Setting.getLabelTags(text);
} },
}, },
{ {
title: i18next.t("video:Label count"), title: i18next.t("video:Label count"),
dataIndex: 'labelCount', dataIndex: "labelCount",
key: 'labelCount', key: "labelCount",
width: '110px', width: "110px",
render: (text, record, index) => { render: (text, record, index) => {
return record.labels.length; return record.labels.length;
} },
}, },
{ {
title: i18next.t("general:Action"), title: i18next.t("general:Action"),
dataIndex: 'action', dataIndex: "action",
key: 'action', key: "action",
width: '80px', width: "80px",
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<div> <div>
<Button style={{marginTop: '10px', marginBottom: '10px', marginRight: '10px'}} type="primary" onClick={() => this.props.history.push(`/videos/${record.name}`)}>{i18next.t("general:Edit")}</Button> <Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/videos/${record.name}`)}>{i18next.t("general:Edit")}</Button>
<Popconfirm <Popconfirm
title={`Sure to delete video: ${record.name} ?`} title={`Sure to delete video: ${record.name} ?`}
onConfirm={() => this.deleteVideo(index)} onConfirm={() => this.deleteVideo(index)}
okText="OK" okText="OK"
cancelText="Cancel" cancelText="Cancel"
> >
<Button style={{marginBottom: '10px'}} type="danger">{i18next.t("general:Delete")}</Button> <Button style={{marginBottom: "10px"}} type="danger">{i18next.t("general:Delete")}</Button>
</Popconfirm> </Popconfirm>
</div> </div>
) );
} },
}, },
]; ];
return ( return (
<div> <div>
<Table columns={columns} dataSource={videos} rowKey="name" size="middle" bordered pagination={{pageSize: 100}} <Table columns={columns} dataSource={videos} rowKey="name" size="middle" bordered pagination={{pageSize: 100}}
title={() => ( title={() => (
<div> <div>
{i18next.t("general:Videos")} {i18next.t("general:Videos")}
{/*&nbsp;&nbsp;&nbsp;&nbsp;*/} {/* &nbsp;&nbsp;&nbsp;&nbsp;*/}
{/*<Button type="primary" size="small" onClick={this.addVideo.bind(this)}>{i18next.t("general:Add")}</Button>*/} {/* <Button type="primary" size="small" onClick={this.addVideo.bind(this)}>{i18next.t("general:Add")}</Button>*/}
&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;
{ {
this.renderUpload() this.renderUpload()
} }
</div> </div>
)} )}
loading={videos === null} loading={videos === null}
/> />
</div> </div>
); );

View File

@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import {Button, Card, Col, Input, InputNumber, Row, Select} from 'antd'; import {Button, Card, Col, Input, InputNumber, Row, Select} from "antd";
import * as WordsetBackend from "./backend/WordsetBackend"; import * as WordsetBackend from "./backend/WordsetBackend";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import i18next from "i18next"; import i18next from "i18next";
@ -7,7 +7,7 @@ import VectorTable from "./VectorTable";
import WordsetGraph from "./WordsetGraph"; import WordsetGraph from "./WordsetGraph";
import * as VectorsetBackend from "./backend/VectorsetBackend"; import * as VectorsetBackend from "./backend/VectorsetBackend";
const { Option } = Select; const {Option} = Select;
class WordsetEditPage extends React.Component { class WordsetEditPage extends React.Component {
constructor(props) { constructor(props) {
@ -54,7 +54,7 @@ class WordsetEditPage extends React.Component {
updateWordsetField(key, value) { updateWordsetField(key, value) {
value = this.parseWordsetField(key, value); value = this.parseWordsetField(key, value);
let wordset = this.state.wordset; const wordset = this.state.wordset;
wordset[key] = value; wordset[key] = value;
this.setState({ this.setState({
wordset: wordset, wordset: wordset,
@ -71,41 +71,41 @@ class WordsetEditPage extends React.Component {
{i18next.t("wordset:Edit Wordset")}&nbsp;&nbsp;&nbsp;&nbsp; {i18next.t("wordset:Edit Wordset")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button type="primary" onClick={this.submitWordsetEdit.bind(this)}>{i18next.t("general:Save")}</Button> <Button type="primary" onClick={this.submitWordsetEdit.bind(this)}>{i18next.t("general:Save")}</Button>
</div> </div>
} style={{marginLeft: '5px'}} type="inner"> } style={{marginLeft: "5px"}} type="inner">
<Row style={{marginTop: '10px'}} > <Row style={{marginTop: "10px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{i18next.t("general:Name")}: {i18next.t("general:Name")}:
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.wordset.name} onChange={e => { <Input value={this.state.wordset.name} onChange={e => {
this.updateWordsetField('name', e.target.value); this.updateWordsetField("name", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{i18next.t("general:Display name")}: {i18next.t("general:Display name")}:
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={this.state.wordset.displayName} onChange={e => { <Input value={this.state.wordset.displayName} onChange={e => {
this.updateWordsetField('displayName', e.target.value); this.updateWordsetField("displayName", e.target.value);
}} /> }} />
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{i18next.t("wordset:Vectorset")}: {i18next.t("wordset:Vectorset")}:
</Col> </Col>
<Col span={22} > <Col span={22} >
<Select virtual={false} style={{width: '100%'}} value={this.state.wordset.vectorset} onChange={(value => {this.updateWordsetField('vectorset', value);})}> <Select virtual={false} style={{width: "100%"}} value={this.state.wordset.vectorset} onChange={(value => {this.updateWordsetField("vectorset", value);})}>
{ {
this.state.vectorsets?.map((vectorset, index) => <Option key={index} value={vectorset.name}>{vectorset.name}</Option>) this.state.vectorsets?.map((vectorset, index) => <Option key={index} value={vectorset.name}>{vectorset.name}</Option>)
} }
</Select> </Select>
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{i18next.t("wordset:Match")}: {i18next.t("wordset:Match")}:
</Col> </Col>
<Col span={22} > <Col span={22} >
@ -123,26 +123,26 @@ class WordsetEditPage extends React.Component {
}}>{i18next.t("wordset:Match")}</Button> }}>{i18next.t("wordset:Match")}</Button>
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{i18next.t("wordset:Matched")}: {i18next.t("wordset:Matched")}:
</Col> </Col>
<Col span={22} > <Col span={22} >
<Input value={`${Setting.getPercentage(allWords === 0 ? 0 : validWords / allWords)}% (${validWords} / ${allWords})`} /> <Input value={`${Setting.getPercentage(allWords === 0 ? 0 : validWords / allWords)}% (${validWords} / ${allWords})`} />
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{i18next.t("wordset:Distance limit")}: {i18next.t("wordset:Distance limit")}:
</Col> </Col>
<Col span={22} > <Col span={22} >
<InputNumber value={this.state.wordset.distanceLimit} onChange={value => { <InputNumber value={this.state.wordset.distanceLimit} onChange={value => {
this.updateWordsetField('distanceLimit', value); this.updateWordsetField("distanceLimit", value);
}} /> }} />
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{i18next.t("wordset:Words")}: {i18next.t("wordset:Words")}:
</Col> </Col>
<Col span={22} > <Col span={22} >
@ -150,12 +150,12 @@ class WordsetEditPage extends React.Component {
title={i18next.t("wordset:Words")} title={i18next.t("wordset:Words")}
table={this.state.wordset.vectors} table={this.state.wordset.vectors}
wordset={this.state.wordset} wordset={this.state.wordset}
onUpdateTable={(value) => { this.updateWordsetField('vectors', value)}} onUpdateTable={(value) => {this.updateWordsetField("vectors", value);}}
/> />
</Col> </Col>
</Row> </Row>
<Row style={{marginTop: '20px'}} > <Row style={{marginTop: "20px"}} >
<Col style={{marginTop: '5px'}} span={(Setting.isMobile()) ? 22 : 2}> <Col style={{marginTop: "5px"}} span={(Setting.isMobile()) ? 22 : 2}>
{i18next.t("general:Preview")}: {i18next.t("general:Preview")}:
</Col> </Col>
<Col span={22} > <Col span={22} >
@ -163,22 +163,22 @@ class WordsetEditPage extends React.Component {
</Col> </Col>
</Row> </Row>
</Card> </Card>
) );
} }
submitWordsetEdit() { submitWordsetEdit() {
let wordset = Setting.deepCopy(this.state.wordset); const wordset = Setting.deepCopy(this.state.wordset);
WordsetBackend.updateWordset(this.state.wordset.owner, this.state.wordsetName, wordset) WordsetBackend.updateWordset(this.state.wordset.owner, this.state.wordsetName, wordset)
.then((res) => { .then((res) => {
if (res) { if (res) {
Setting.showMessage("success", `Successfully saved`); Setting.showMessage("success", "Successfully saved");
this.setState({ this.setState({
wordsetName: this.state.wordset.name, wordsetName: this.state.wordset.name,
}); });
this.props.history.push(`/wordsets/${this.state.wordset.name}`); this.props.history.push(`/wordsets/${this.state.wordset.name}`);
} else { } else {
Setting.showMessage("error", `failed to save: server side failure`); Setting.showMessage("error", "failed to save: server side failure");
this.updateWordsetField('name', this.state.wordsetName); this.updateWordsetField("name", this.state.wordsetName);
} }
}) })
.catch(error => { .catch(error => {

View File

@ -1,7 +1,7 @@
import React from "react"; import React from "react";
import {Button, Card, Col, Empty, InputNumber, List, Row, Select, Slider, Spin, Switch, Tooltip} from "antd"; import {Button, Card, Col, Empty, InputNumber, List, Row, Select, Slider, Spin, Switch, Tooltip} from "antd";
import ForceGraph2D from 'react-force-graph-2d'; import ForceGraph2D from "react-force-graph-2d";
import ForceGraph3D from 'react-force-graph-3d'; import ForceGraph3D from "react-force-graph-3d";
import * as d3 from "d3-force"; import * as d3 from "d3-force";
import * as WordsetBackend from "./backend/WordsetBackend"; import * as WordsetBackend from "./backend/WordsetBackend";
import i18next from "i18next"; import i18next from "i18next";
@ -9,9 +9,9 @@ import FileSaver from "file-saver";
import XLSX from "xlsx"; import XLSX from "xlsx";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
const { Option } = Select; const {Option} = Select;
let fg = React.createRef(); const fg = React.createRef();
class ForceGraph extends React.Component { class ForceGraph extends React.Component {
constructor(props) { constructor(props) {
@ -25,11 +25,11 @@ class ForceGraph extends React.Component {
if (!this.props.enable3D) { if (!this.props.enable3D) {
return ( return (
<ForceGraph2D ref={fg} {...this.props} /> <ForceGraph2D ref={fg} {...this.props} />
) );
} else { } else {
return ( return (
<ForceGraph3D ref={fg} {...this.props} /> <ForceGraph3D ref={fg} {...this.props} />
) );
} }
} }
} }
@ -54,7 +54,7 @@ class WordsetGraph extends React.Component {
selectedType: null, selectedType: null,
selectedId: null, selectedId: null,
selectedIds: [], selectedIds: [],
} };
} }
UNSAFE_componentWillMount() { UNSAFE_componentWillMount() {
@ -62,7 +62,7 @@ class WordsetGraph extends React.Component {
} }
componentDidUpdate(prevProps, prevState, snapshot) { componentDidUpdate(prevProps, prevState, snapshot) {
fg.current?.d3Force('collision', d3.forceCollide(15)); fg.current?.d3Force("collision", d3.forceCollide(15));
} }
getWordsetGraph() { getWordsetGraph() {
@ -84,7 +84,7 @@ class WordsetGraph extends React.Component {
downloadClusters() { downloadClusters() {
let data = []; let data = [];
this.state.graph.nodes.forEach((node, i) => { this.state.graph.nodes.forEach((node, i) => {
let row = {}; const row = {};
row[0] = node.id; row[0] = node.id;
row[1] = parseInt(node.tag) + 1; row[1] = parseInt(node.tag) + 1;
@ -93,7 +93,7 @@ class WordsetGraph extends React.Component {
data = data.sort((a, b) => {return a[1] - b[1];}); data = data.sort((a, b) => {return a[1] - b[1];});
let sheet = XLSX.utils.json_to_sheet(data, {skipHeader: true}); const sheet = XLSX.utils.json_to_sheet(data, {skipHeader: true});
const blob = Setting.sheet2blob(sheet, "clusters"); const blob = Setting.sheet2blob(sheet, "clusters");
const fileName = `clusters-${this.state.wordsetName}-${this.state.graph.nodes.length}-${this.state.clusterNumber}.xlsx`; const fileName = `clusters-${this.state.wordsetName}-${this.state.graph.nodes.length}-${this.state.clusterNumber}.xlsx`;
FileSaver.saveAs(blob, fileName); FileSaver.saveAs(blob, fileName);
@ -117,11 +117,11 @@ class WordsetGraph extends React.Component {
<List.Item> <List.Item>
{`${(link.source !== node.id && link.source.id !== node.id) ? link.source.id : link.target.id} | ${link.tag}`} {`${(link.source !== node.id && link.source.id !== node.id) ? link.source.id : link.target.id} | ${link.tag}`}
</List.Item> </List.Item>
) );
}} }}
pagination={{pageSize: 20, size: "small", showLessItems: true, simple: true}} pagination={{pageSize: 20, size: "small", showLessItems: true, simple: true}}
/> />
) );
} }
renderNodeMenu(selectedIds) { renderNodeMenu(selectedIds) {
@ -142,11 +142,11 @@ class WordsetGraph extends React.Component {
{`${node.id} | ${node.weight}`} {`${node.id} | ${node.weight}`}
</List.Item> </List.Item>
</Tooltip> </Tooltip>
) );
}} }}
pagination={{pageSize: 20, size: "small", showLessItems: true, simple: true}} pagination={{pageSize: 20, size: "small", showLessItems: true, simple: true}}
/> />
) );
} }
renderLeftToolbar() { renderLeftToolbar() {
@ -165,7 +165,7 @@ class WordsetGraph extends React.Component {
return ( return (
<div style={{width: "100%", position: "fixed", zIndex: 2, pointerEvents: "none"}}> <div style={{width: "100%", position: "fixed", zIndex: 2, pointerEvents: "none"}}>
<div style={{width: "200px", marginLeft: '20px', marginTop: '20px', pointerEvents: "auto"}}> <div style={{width: "200px", marginLeft: "20px", marginTop: "20px", pointerEvents: "auto"}}>
<Card style={{marginTop: "20px"}} size="small" title={ <Card style={{marginTop: "20px"}} size="small" title={
<div> <div>
分类{categoryName} 分类{categoryName}
@ -177,25 +177,25 @@ class WordsetGraph extends React.Component {
</Card> </Card>
</div> </div>
</div> </div>
) );
} }
renderRightToolbar() { renderRightToolbar() {
return ( return (
<div style={{width: "100%", position: "fixed", zIndex: 2, pointerEvents: "none"}}> <div style={{width: "100%", position: "fixed", zIndex: 2, pointerEvents: "none"}}>
<div style={{width: "200px", float: "right", marginRight: '20px', marginTop: '20px', pointerEvents: "auto"}}> <div style={{width: "200px", float: "right", marginRight: "20px", marginTop: "20px", pointerEvents: "auto"}}>
<Card size="small" title={ <Card size="small" title={
<div> <div>
图形选项 图形选项
</div> </div>
} type="inner"> } type="inner">
<Row> <Row>
<Col style={{marginTop: '5px', textAlign: 'center'}} span={12}> <Col style={{marginTop: "5px", textAlign: "center"}} span={12}>
<span style={{verticalAlign: "middle"}}> <span style={{verticalAlign: "middle"}}>
保持静止 保持静止
</span> </span>
</Col> </Col>
<Col style={{marginTop: '5px', textAlign: 'center'}} span={12}> <Col style={{marginTop: "5px", textAlign: "center"}} span={12}>
<Switch checked={this.state.enableStatic} onChange={(checked, e) => { <Switch checked={this.state.enableStatic} onChange={(checked, e) => {
this.setState({ this.setState({
enableStatic: checked, enableStatic: checked,
@ -203,7 +203,7 @@ class WordsetGraph extends React.Component {
}} /> }} />
</Col> </Col>
</Row> </Row>
{/*<Row>*/} {/* <Row>*/}
{/* <Col style={{marginTop: '5px', textAlign: 'center'}} span={12}>*/} {/* <Col style={{marginTop: '5px', textAlign: 'center'}} span={12}>*/}
{/* 显示曲线:*/} {/* 显示曲线:*/}
{/* </Col>*/} {/* </Col>*/}
@ -214,14 +214,14 @@ class WordsetGraph extends React.Component {
{/* });*/} {/* });*/}
{/* }} />*/} {/* }} />*/}
{/* </Col>*/} {/* </Col>*/}
{/*</Row>*/} {/* </Row>*/}
<Row> <Row>
<Col style={{marginTop: '5px', textAlign: 'center'}} span={12}> <Col style={{marginTop: "5px", textAlign: "center"}} span={12}>
<span style={{verticalAlign: "middle"}}> <span style={{verticalAlign: "middle"}}>
启用粗线 启用粗线
</span> </span>
</Col> </Col>
<Col style={{marginTop: '5px', textAlign: 'center'}} span={12}> <Col style={{marginTop: "5px", textAlign: "center"}} span={12}>
<Switch checked={this.state.enableBolderLink} onChange={(checked, e) => { <Switch checked={this.state.enableBolderLink} onChange={(checked, e) => {
this.setState({ this.setState({
enableBolderLink: checked, enableBolderLink: checked,
@ -230,12 +230,12 @@ class WordsetGraph extends React.Component {
</Col> </Col>
</Row> </Row>
<Row> <Row>
<Col style={{marginTop: '5px', textAlign: 'center'}} span={12}> <Col style={{marginTop: "5px", textAlign: "center"}} span={12}>
<span style={{verticalAlign: "middle"}}> <span style={{verticalAlign: "middle"}}>
启用3D 启用3D
</span> </span>
</Col> </Col>
<Col style={{marginTop: '5px', textAlign: 'center'}} span={12}> <Col style={{marginTop: "5px", textAlign: "center"}} span={12}>
<Switch checked={this.state.enable3D} onChange={(checked, e) => { <Switch checked={this.state.enable3D} onChange={(checked, e) => {
this.setState({ this.setState({
enable3D: checked, enable3D: checked,
@ -243,7 +243,7 @@ class WordsetGraph extends React.Component {
}} /> }} />
</Col> </Col>
</Row> </Row>
{/*<Row>*/} {/* <Row>*/}
{/* <Col style={{marginTop: '5px', textAlign: 'center'}} span={12}>*/} {/* <Col style={{marginTop: '5px', textAlign: 'center'}} span={12}>*/}
{/* 粒子数量:*/} {/* 粒子数量:*/}
{/* </Col>*/} {/* </Col>*/}
@ -254,38 +254,38 @@ class WordsetGraph extends React.Component {
{/* });*/} {/* });*/}
{/* })} />*/} {/* })} />*/}
{/* </Col>*/} {/* </Col>*/}
{/*</Row>*/} {/* </Row>*/}
<Row> <Row>
<Col style={{marginTop: '5px', textAlign: 'center'}} span={12}> <Col style={{marginTop: "5px", textAlign: "center"}} span={12}>
<span style={{verticalAlign: "middle"}}> <span style={{verticalAlign: "middle"}}>
扩散度 扩散度
</span> </span>
</Col> </Col>
<Col style={{marginTop: '5px', textAlign: 'center'}} span={12}> <Col style={{marginTop: "5px", textAlign: "center"}} span={12}>
<Slider value={this.state.strength} dots={true} min={0} max={1000} onChange={(value => { <Slider value={this.state.strength} dots={true} min={0} max={1000} onChange={(value => {
this.setState({ this.setState({
strength: value, strength: value,
}); });
// https://github.com/vasturiano/react-force-graph/issues/25 // https://github.com/vasturiano/react-force-graph/issues/25
fg.current.d3Force('charge').strength(-value); fg.current.d3Force("charge").strength(-value);
})} /> })} />
</Col> </Col>
</Row> </Row>
<Row> <Row>
<Col style={{marginTop: '5px', textAlign: 'center'}} span={12}> <Col style={{marginTop: "5px", textAlign: "center"}} span={12}>
<span style={{verticalAlign: "middle"}}> <span style={{verticalAlign: "middle"}}>
最大距离 最大距离
</span> </span>
</Col> </Col>
<Col style={{marginTop: '5px', textAlign: 'center'}} span={12}> <Col style={{marginTop: "5px", textAlign: "center"}} span={12}>
<Slider value={this.state.distanceMax} dots={true} min={0} max={1000} onChange={(value => { <Slider value={this.state.distanceMax} dots={true} min={0} max={1000} onChange={(value => {
this.setState({ this.setState({
distanceMax: value, distanceMax: value,
}); });
// https://github.com/vasturiano/react-force-graph/issues/25 // https://github.com/vasturiano/react-force-graph/issues/25
fg.current.d3Force('charge').distanceMax(value); fg.current.d3Force("charge").distanceMax(value);
})} /> })} />
</Col> </Col>
</Row> </Row>
@ -296,32 +296,32 @@ class WordsetGraph extends React.Component {
</div> </div>
} type="inner"> } type="inner">
<Row> <Row>
<Col style={{marginTop: '5px', textAlign: 'center'}} span={8}> <Col style={{marginTop: "5px", textAlign: "center"}} span={8}>
<span style={{verticalAlign: "middle"}}> <span style={{verticalAlign: "middle"}}>
算法 算法
</span> </span>
</Col> </Col>
<Col style={{marginTop: '5px', textAlign: 'center'}} span={16}> <Col style={{marginTop: "5px", textAlign: "center"}} span={16}>
<Select virtual={false} style={{width: '100%'}} value={"K-Means"} onChange={(value => { <Select virtual={false} style={{width: "100%"}} value={"K-Means"} onChange={(value => {
this.setState({ this.setState({
algorithm: value, algorithm: value,
}); });
})}> })}>
{ {
[ [
{id: 'K-Means', name: 'K-Means'}, {id: "K-Means", name: "K-Means"},
].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>) ].map((item, index) => <Option key={index} value={item.id}>{item.name}</Option>)
} }
</Select> </Select>
</Col> </Col>
</Row> </Row>
<Row> <Row>
<Col style={{marginTop: '5px', textAlign: 'center'}} span={12}> <Col style={{marginTop: "5px", textAlign: "center"}} span={12}>
<span style={{verticalAlign: "middle"}}> <span style={{verticalAlign: "middle"}}>
聚类个数 聚类个数
</span> </span>
</Col> </Col>
<Col style={{marginTop: '5px', textAlign: 'center'}} span={12}> <Col style={{marginTop: "5px", textAlign: "center"}} span={12}>
<InputNumber style={{width: "100%"}} min={2} max={this.state.graph?.nodes.length} step={1} value={this.state.clusterNumber} onChange={value => { <InputNumber style={{width: "100%"}} min={2} max={this.state.graph?.nodes.length} step={1} value={this.state.clusterNumber} onChange={value => {
this.setState({ this.setState({
clusterNumber: value, clusterNumber: value,
@ -330,12 +330,12 @@ class WordsetGraph extends React.Component {
</Col> </Col>
</Row> </Row>
<Row> <Row>
<Col style={{marginTop: '5px', textAlign: 'center'}} span={12}> <Col style={{marginTop: "5px", textAlign: "center"}} span={12}>
<span style={{verticalAlign: "middle"}}> <span style={{verticalAlign: "middle"}}>
距离上限 距离上限
</span> </span>
</Col> </Col>
<Col style={{marginTop: '5px', textAlign: 'center'}} span={12}> <Col style={{marginTop: "5px", textAlign: "center"}} span={12}>
<InputNumber style={{width: "100%"}} min={1} max={100} step={1} value={this.state.distanceLimit} onChange={value => { <InputNumber style={{width: "100%"}} min={1} max={100} step={1} value={this.state.distanceLimit} onChange={value => {
this.setState({ this.setState({
distanceLimit: value, distanceLimit: value,
@ -343,8 +343,8 @@ class WordsetGraph extends React.Component {
}} /> }} />
</Col> </Col>
</Row> </Row>
<Row style={{textAlign: "center", paddingTop: '10px'}}> <Row style={{textAlign: "center", paddingTop: "10px"}}>
<Button style={{margin: 'auto'}} type="primary" onClick={() => { <Button style={{margin: "auto"}} type="primary" onClick={() => {
this.setState({ this.setState({
graph: null, graph: null,
}); });
@ -352,7 +352,7 @@ class WordsetGraph extends React.Component {
}}> }}>
重新聚类 重新聚类
</Button> </Button>
<Button style={{margin: 'auto', marginTop: '10px'}} onClick={() => { <Button style={{margin: "auto", marginTop: "10px"}} onClick={() => {
this.downloadClusters(); this.downloadClusters();
}}> }}>
下载结果 下载结果
@ -361,7 +361,7 @@ class WordsetGraph extends React.Component {
</Card> </Card>
</div> </div>
</div> </div>
) );
} }
getNodeId(node) { getNodeId(node) {
@ -379,7 +379,7 @@ class WordsetGraph extends React.Component {
<div className="App"> <div className="App">
<Spin size="large" tip={i18next.t("general:Loading...")} style={{paddingTop: "10%"}} /> <Spin size="large" tip={i18next.t("general:Loading...")} style={{paddingTop: "10%"}} />
</div> </div>
) );
} }
if (this.state.graph === null || this.state.graph.nodes === null || this.state.graph.links === null) { if (this.state.graph === null || this.state.graph.nodes === null || this.state.graph.links === null) {
@ -387,7 +387,7 @@ class WordsetGraph extends React.Component {
<div className="App"> <div className="App">
<Empty style={{paddingTop: "10%"}} image={Empty.PRESENTED_IMAGE_SIMPLE} /> <Empty style={{paddingTop: "10%"}} image={Empty.PRESENTED_IMAGE_SIMPLE} />
</div> </div>
) );
} }
// highlight example // highlight example
@ -445,12 +445,12 @@ class WordsetGraph extends React.Component {
if (this.state.selectedId === node.id) { if (this.state.selectedId === node.id) {
ctx.beginPath(); ctx.beginPath();
ctx.arc(node.x, node.y, node.val * 1.7, 0, 2 * Math.PI, false); ctx.arc(node.x, node.y, node.val * 1.7, 0, 2 * Math.PI, false);
ctx.fillStyle = 'red'; ctx.fillStyle = "red";
ctx.fill(); ctx.fill();
} else if (this.state.selectedIds.filter(n => n.id === node.id).length > 0) { } else if (this.state.selectedIds.filter(n => n.id === node.id).length > 0) {
ctx.beginPath(); ctx.beginPath();
ctx.arc(node.x, node.y, node.val * 1.4, 0, 2 * Math.PI, false); ctx.arc(node.x, node.y, node.val * 1.4, 0, 2 * Math.PI, false);
ctx.fillStyle = 'red'; ctx.fillStyle = "red";
ctx.fill(); ctx.fill();
} else if (this.state.selectedId !== null) { } else if (this.state.selectedId !== null) {
ctx.globalAlpha = 0.3; ctx.globalAlpha = 0.3;
@ -460,20 +460,20 @@ class WordsetGraph extends React.Component {
ctx.fillStyle = node.color; ctx.fillStyle = node.color;
ctx.arc(node.x, node.y, node.val, 0, 2 * Math.PI, false); ctx.arc(node.x, node.y, node.val, 0, 2 * Math.PI, false);
ctx.fill(); ctx.fill();
ctx.strokeStyle = 'rgb(255,255,255)'; ctx.strokeStyle = "rgb(255,255,255)";
ctx.stroke(); ctx.stroke();
ctx.font = '5px Lucida Console'; ctx.font = "5px Lucida Console";
ctx.textAlign = 'center'; ctx.textAlign = "center";
ctx.textBaseline = 'middle'; ctx.textBaseline = "middle";
ctx.fillStyle = 'black'; //node.color; ctx.fillStyle = "black"; // node.color;
ctx.fillText(node.name, node.x, node.y + 10); ctx.fillText(node.name, node.x, node.y + 10);
ctx.restore(); ctx.restore();
}} }}
/> />
) );
} }
render() { render() {

View File

@ -27,8 +27,8 @@ class WordsetGraphPage extends React.Component {
render() { render() {
return (this.state.wordset === undefined || this.state.wordset === null) ? null : ( return (this.state.wordset === undefined || this.state.wordset === null) ? null : (
<WordsetGraph wordset={this.state.wordset} wordsetName={this.state.wordset.name}/> <WordsetGraph wordset={this.state.wordset} wordsetName={this.state.wordset.name} />
) );
} }
} }

View File

@ -1,6 +1,6 @@
import React from "react"; import React from "react";
import {Link} from "react-router-dom"; import {Link} from "react-router-dom";
import {Button, Col, Popconfirm, Row, Table} from 'antd'; import {Button, Col, Popconfirm, Row, Table} from "antd";
import moment from "moment"; import moment from "moment";
import * as Setting from "./Setting"; import * as Setting from "./Setting";
import * as WordsetBackend from "./backend/WordsetBackend"; import * as WordsetBackend from "./backend/WordsetBackend";
@ -37,14 +37,14 @@ class WordsetListPage extends React.Component {
distanceLimit: 14, distanceLimit: 14,
vectorset: "wordVector_utf-8", vectorset: "wordVector_utf-8",
vectors: [], vectors: [],
} };
} }
addWordset() { addWordset() {
const newWordset = this.newWordset(); const newWordset = this.newWordset();
WordsetBackend.addWordset(newWordset) WordsetBackend.addWordset(newWordset)
.then((res) => { .then((res) => {
Setting.showMessage("success", `Wordset added successfully`); Setting.showMessage("success", "Wordset added successfully");
this.setState({ this.setState({
wordsets: Setting.prependRow(this.state.wordsets, newWordset), wordsets: Setting.prependRow(this.state.wordsets, newWordset),
}); });
@ -57,7 +57,7 @@ class WordsetListPage extends React.Component {
deleteWordset(i) { deleteWordset(i) {
WordsetBackend.deleteWordset(this.state.wordsets[i]) WordsetBackend.deleteWordset(this.state.wordsets[i])
.then((res) => { .then((res) => {
Setting.showMessage("success", `Wordset deleted successfully`); Setting.showMessage("success", "Wordset deleted successfully");
this.setState({ this.setState({
wordsets: Setting.deleteRow(this.state.wordsets, i), wordsets: Setting.deleteRow(this.state.wordsets, i),
}); });
@ -71,34 +71,34 @@ class WordsetListPage extends React.Component {
const columns = [ const columns = [
{ {
title: i18next.t("general:Name"), title: i18next.t("general:Name"),
dataIndex: 'name', dataIndex: "name",
key: 'name', key: "name",
width: '120px', width: "120px",
sorter: (a, b) => a.name.localeCompare(b.name), sorter: (a, b) => a.name.localeCompare(b.name),
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Link to={`/wordsets/${text}`}> <Link to={`/wordsets/${text}`}>
{text} {text}
</Link> </Link>
) );
} },
}, },
{ {
title: i18next.t("general:Display name"), title: i18next.t("general:Display name"),
dataIndex: 'displayName', dataIndex: "displayName",
key: 'displayName', key: "displayName",
width: '200px', width: "200px",
sorter: (a, b) => a.displayName.localeCompare(b.displayName), sorter: (a, b) => a.displayName.localeCompare(b.displayName),
}, },
{ {
title: i18next.t("wordset:Words"), title: i18next.t("wordset:Words"),
dataIndex: 'vectors', dataIndex: "vectors",
key: 'vectors', key: "vectors",
// width: '120px', // width: '120px',
sorter: (a, b) => a.vectors.localeCompare(b.vectors), sorter: (a, b) => a.vectors.localeCompare(b.vectors),
render: (text, record, index) => { render: (text, record, index) => {
return Setting.getTags(text); return Setting.getTags(text);
} },
}, },
// { // {
// title: i18next.t("wordset:All words"), // title: i18next.t("wordset:All words"),
@ -122,71 +122,71 @@ class WordsetListPage extends React.Component {
// }, // },
{ {
title: i18next.t("wordset:Vectorset"), title: i18next.t("wordset:Vectorset"),
dataIndex: 'vectorset', dataIndex: "vectorset",
key: 'vectorset', key: "vectorset",
width: '140px', width: "140px",
sorter: (a, b) => a.vectorset.localeCompare(b.vectorset), sorter: (a, b) => a.vectorset.localeCompare(b.vectorset),
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<Link to={`/vectorsets/${text}`}> <Link to={`/vectorsets/${text}`}>
{text} {text}
</Link> </Link>
) );
} },
}, },
{ {
title: i18next.t("wordset:Matched"), title: i18next.t("wordset:Matched"),
dataIndex: 'matched', dataIndex: "matched",
key: 'matched', key: "matched",
width: '140px', width: "140px",
render: (text, record, index) => { render: (text, record, index) => {
const allWords = record.vectors.length; const allWords = record.vectors.length;
const validWords = record.vectors.filter(vector => vector.data.length !== 0).length; const validWords = record.vectors.filter(vector => vector.data.length !== 0).length;
return `${Setting.getPercentage(allWords === 0 ? 0 : validWords / allWords)}% (${validWords} / ${allWords})`; return `${Setting.getPercentage(allWords === 0 ? 0 : validWords / allWords)}% (${validWords} / ${allWords})`;
} },
}, },
{ {
title: i18next.t("wordset:Distance limit"), title: i18next.t("wordset:Distance limit"),
dataIndex: 'distanceLimit', dataIndex: "distanceLimit",
key: 'distanceLimit', key: "distanceLimit",
width: '120px', width: "120px",
sorter: (a, b) => a.distanceLimit - b.distanceLimit, sorter: (a, b) => a.distanceLimit - b.distanceLimit,
}, },
{ {
title: i18next.t("general:Action"), title: i18next.t("general:Action"),
dataIndex: 'action', dataIndex: "action",
key: 'action', key: "action",
width: '80px', width: "80px",
render: (text, record, index) => { render: (text, record, index) => {
return ( return (
<div> <div>
<Button style={{marginTop: '10px', marginBottom: '10px', marginRight: '10px'}} onClick={() => Setting.openLink(`/wordsets/${record.name}/graph`)}>{i18next.t("general:Result")}</Button> <Button style={{marginTop: "10px", marginBottom: "10px", marginRight: "10px"}} onClick={() => Setting.openLink(`/wordsets/${record.name}/graph`)}>{i18next.t("general:Result")}</Button>
<Button style={{marginBottom: '10px', marginRight: '10px'}} onClick={() => Setting.downloadXlsx(record)}>{i18next.t("general:Download")}</Button> <Button style={{marginBottom: "10px", marginRight: "10px"}} onClick={() => Setting.downloadXlsx(record)}>{i18next.t("general:Download")}</Button>
<Button style={{marginBottom: '10px', marginRight: '10px'}} type="primary" onClick={() => this.props.history.push(`/wordsets/${record.name}`)}>{i18next.t("general:Edit")}</Button> <Button style={{marginBottom: "10px", marginRight: "10px"}} type="primary" onClick={() => this.props.history.push(`/wordsets/${record.name}`)}>{i18next.t("general:Edit")}</Button>
<Popconfirm <Popconfirm
title={`Sure to delete wordset: ${record.name} ?`} title={`Sure to delete wordset: ${record.name} ?`}
onConfirm={() => this.deleteWordset(index)} onConfirm={() => this.deleteWordset(index)}
okText="OK" okText="OK"
cancelText="Cancel" cancelText="Cancel"
> >
<Button style={{marginBottom: '10px'}} type="danger">{i18next.t("general:Delete")}</Button> <Button style={{marginBottom: "10px"}} type="danger">{i18next.t("general:Delete")}</Button>
</Popconfirm> </Popconfirm>
</div> </div>
) );
} },
}, },
]; ];
return ( return (
<div> <div>
<Table columns={columns} dataSource={wordsets} rowKey="name" size="middle" bordered pagination={{pageSize: 100}} <Table columns={columns} dataSource={wordsets} rowKey="name" size="middle" bordered pagination={{pageSize: 100}}
title={() => ( title={() => (
<div> <div>
{i18next.t("general:Wordsets")}&nbsp;&nbsp;&nbsp;&nbsp; {i18next.t("general:Wordsets")}&nbsp;&nbsp;&nbsp;&nbsp;
<Button type="primary" size="small" onClick={this.addWordset.bind(this)}>{i18next.t("general:Add")}</Button> <Button type="primary" size="small" onClick={this.addWordset.bind(this)}>{i18next.t("general:Add")}</Button>
</div> </div>
)} )}
loading={wordsets === null} loading={wordsets === null}
/> />
</div> </div>
); );

View File

@ -1,22 +1,22 @@
import * as Setting from "../Setting"; import * as Setting from "../Setting";
export function getAccount() { export function getAccount() {
return fetch(`${Setting.ServerUrl}/api/get-account`, { return fetch(`${Setting.ServerUrl}/api/get-account`, {
method: 'GET', method: "GET",
credentials: 'include' credentials: "include",
}).then(res => res.json()); }).then(res => res.json());
} }
export function signin(code, state) { export function signin(code, state) {
return fetch(`${Setting.ServerUrl}/api/signin?code=${code}&state=${state}`, { return fetch(`${Setting.ServerUrl}/api/signin?code=${code}&state=${state}`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
}).then(res => res.json()); }).then(res => res.json());
} }
export function signout() { export function signout() {
return fetch(`${Setting.ServerUrl}/api/signout`, { return fetch(`${Setting.ServerUrl}/api/signout`, {
method: 'POST', method: "POST",
credentials: "include", credentials: "include",
}).then(res => res.json()); }).then(res => res.json());
} }

View File

@ -1,34 +1,34 @@
import * as Setting from "../Setting"; import * as Setting from "../Setting";
export function updateFile(storeId, name, file) { export function updateFile(storeId, name, file) {
let newFile = Setting.deepCopy(file); const newFile = Setting.deepCopy(file);
return fetch(`${Setting.ServerUrl}/api/update-file?store=${storeId}&name=${name}`, { return fetch(`${Setting.ServerUrl}/api/update-file?store=${storeId}&name=${name}`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
body: JSON.stringify(newFile), body: JSON.stringify(newFile),
}).then(res => res.json()); }).then(res => res.json());
} }
export function addFile(storeId, key, isLeaf, filename, file) { export function addFile(storeId, key, isLeaf, filename, file) {
let formData = new FormData(); const formData = new FormData();
formData.append("file", file); formData.append("file", file);
return fetch(`${Setting.ServerUrl}/api/add-file?store=${storeId}&key=${key}&isLeaf=${isLeaf ? 1 : 0}&filename=${filename}`, { return fetch(`${Setting.ServerUrl}/api/add-file?store=${storeId}&key=${key}&isLeaf=${isLeaf ? 1 : 0}&filename=${filename}`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
body: formData, body: formData,
}).then(res => res.json()); }).then(res => res.json());
} }
export function deleteFile(storeId, key, isLeaf) { export function deleteFile(storeId, key, isLeaf) {
return fetch(`${Setting.ServerUrl}/api/delete-file?store=${storeId}&key=${key}&isLeaf=${isLeaf ? 1 : 0}`, { return fetch(`${Setting.ServerUrl}/api/delete-file?store=${storeId}&key=${key}&isLeaf=${isLeaf ? 1 : 0}`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
}).then(res => res.json()); }).then(res => res.json());
} }
export function activateFile(key, filename) { export function activateFile(key, filename) {
return fetch(`${Setting.ServerUrl}/api/activate-file?key=${key}&filename=${filename}`, { return fetch(`${Setting.ServerUrl}/api/activate-file?key=${key}&filename=${filename}`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
}).then(res => res.json()); }).then(res => res.json());
} }

View File

@ -3,47 +3,47 @@ import * as Setting from "../Setting";
export function getGlobalPermissions() { export function getGlobalPermissions() {
return fetch(`${Setting.ServerUrl}/api/get-global-permissions`, { return fetch(`${Setting.ServerUrl}/api/get-global-permissions`, {
method: "GET", method: "GET",
credentials: "include" credentials: "include",
}).then(res => res.json()); }).then(res => res.json());
} }
export function getPermissions(owner) { export function getPermissions(owner) {
return fetch(`${Setting.ServerUrl}/api/get-permissions?owner=${owner}`, { return fetch(`${Setting.ServerUrl}/api/get-permissions?owner=${owner}`, {
method: "GET", method: "GET",
credentials: "include" credentials: "include",
}).then(res => res.json()); }).then(res => res.json());
} }
export function getPermission(owner, name) { export function getPermission(owner, name) {
return fetch(`${Setting.ServerUrl}/api/get-permission?id=${owner}/${encodeURIComponent(name)}`, { return fetch(`${Setting.ServerUrl}/api/get-permission?id=${owner}/${encodeURIComponent(name)}`, {
method: "GET", method: "GET",
credentials: "include" credentials: "include",
}).then(res => res.json()); }).then(res => res.json());
} }
export function updatePermission(owner, name, permission) { export function updatePermission(owner, name, permission) {
let newPermission = Setting.deepCopy(permission); const newPermission = Setting.deepCopy(permission);
return fetch(`${Setting.ServerUrl}/api/update-permission?id=${owner}/${encodeURIComponent(name)}`, { return fetch(`${Setting.ServerUrl}/api/update-permission?id=${owner}/${encodeURIComponent(name)}`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
body: JSON.stringify(newPermission), body: JSON.stringify(newPermission),
}).then(res => res.json()); }).then(res => res.json());
} }
export function addPermission(permission) { export function addPermission(permission) {
let newPermission = Setting.deepCopy(permission); const newPermission = Setting.deepCopy(permission);
return fetch(`${Setting.ServerUrl}/api/add-permission`, { return fetch(`${Setting.ServerUrl}/api/add-permission`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
body: JSON.stringify(newPermission), body: JSON.stringify(newPermission),
}).then(res => res.json()); }).then(res => res.json());
} }
export function deletePermission(permission) { export function deletePermission(permission) {
let newPermission = Setting.deepCopy(permission); const newPermission = Setting.deepCopy(permission);
return fetch(`${Setting.ServerUrl}/api/delete-permission`, { return fetch(`${Setting.ServerUrl}/api/delete-permission`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
body: JSON.stringify(newPermission), body: JSON.stringify(newPermission),
}).then(res => res.json()); }).then(res => res.json());
} }

View File

@ -3,47 +3,47 @@ import * as Setting from "../Setting";
export function getGlobalStores() { export function getGlobalStores() {
return fetch(`${Setting.ServerUrl}/api/get-global-stores`, { return fetch(`${Setting.ServerUrl}/api/get-global-stores`, {
method: "GET", method: "GET",
credentials: "include" credentials: "include",
}).then(res => res.json()); }).then(res => res.json());
} }
export function getStores(owner) { export function getStores(owner) {
return fetch(`${Setting.ServerUrl}/api/get-stores?owner=${owner}`, { return fetch(`${Setting.ServerUrl}/api/get-stores?owner=${owner}`, {
method: "GET", method: "GET",
credentials: "include" credentials: "include",
}).then(res => res.json()); }).then(res => res.json());
} }
export function getStore(owner, name) { export function getStore(owner, name) {
return fetch(`${Setting.ServerUrl}/api/get-store?id=${owner}/${encodeURIComponent(name)}`, { return fetch(`${Setting.ServerUrl}/api/get-store?id=${owner}/${encodeURIComponent(name)}`, {
method: "GET", method: "GET",
credentials: "include" credentials: "include",
}).then(res => res.json()); }).then(res => res.json());
} }
export function updateStore(owner, name, store) { export function updateStore(owner, name, store) {
let newStore = Setting.deepCopy(store); const newStore = Setting.deepCopy(store);
return fetch(`${Setting.ServerUrl}/api/update-store?id=${owner}/${encodeURIComponent(name)}`, { return fetch(`${Setting.ServerUrl}/api/update-store?id=${owner}/${encodeURIComponent(name)}`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
body: JSON.stringify(newStore), body: JSON.stringify(newStore),
}).then(res => res.json()); }).then(res => res.json());
} }
export function addStore(store) { export function addStore(store) {
let newStore = Setting.deepCopy(store); const newStore = Setting.deepCopy(store);
return fetch(`${Setting.ServerUrl}/api/add-store`, { return fetch(`${Setting.ServerUrl}/api/add-store`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
body: JSON.stringify(newStore), body: JSON.stringify(newStore),
}).then(res => res.json()); }).then(res => res.json());
} }
export function deleteStore(store) { export function deleteStore(store) {
let newStore = Setting.deepCopy(store); const newStore = Setting.deepCopy(store);
return fetch(`${Setting.ServerUrl}/api/delete-store`, { return fetch(`${Setting.ServerUrl}/api/delete-store`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
body: JSON.stringify(newStore), body: JSON.stringify(newStore),
}).then(res => res.json()); }).then(res => res.json());
} }

View File

@ -3,54 +3,54 @@ import * as Setting from "../Setting";
export function getGlobalVectorsets() { export function getGlobalVectorsets() {
return fetch(`${Setting.ServerUrl}/api/get-global-vectorsets`, { return fetch(`${Setting.ServerUrl}/api/get-global-vectorsets`, {
method: "GET", method: "GET",
credentials: "include" credentials: "include",
}).then(res => res.json()); }).then(res => res.json());
} }
export function getVectorsets(owner) { export function getVectorsets(owner) {
return fetch(`${Setting.ServerUrl}/api/get-vectorsets?owner=${owner}`, { return fetch(`${Setting.ServerUrl}/api/get-vectorsets?owner=${owner}`, {
method: "GET", method: "GET",
credentials: "include" credentials: "include",
}).then(res => res.json()); }).then(res => res.json());
} }
export function getVectorset(owner, name) { export function getVectorset(owner, name) {
return fetch(`${Setting.ServerUrl}/api/get-vectorset?id=${owner}/${encodeURIComponent(name)}`, { return fetch(`${Setting.ServerUrl}/api/get-vectorset?id=${owner}/${encodeURIComponent(name)}`, {
method: "GET", method: "GET",
credentials: "include" credentials: "include",
}).then(res => res.json()); }).then(res => res.json());
} }
export function getVectorsetGraph(owner, name, clusterNumber, distanceLimit) { export function getVectorsetGraph(owner, name, clusterNumber, distanceLimit) {
return fetch(`${Setting.ServerUrl}/api/get-vectorset-graph?id=${owner}/${encodeURIComponent(name)}&clusterNumber=${clusterNumber}&distanceLimit=${distanceLimit}`, { return fetch(`${Setting.ServerUrl}/api/get-vectorset-graph?id=${owner}/${encodeURIComponent(name)}&clusterNumber=${clusterNumber}&distanceLimit=${distanceLimit}`, {
method: "GET", method: "GET",
credentials: "include" credentials: "include",
}).then(res => res.json()); }).then(res => res.json());
} }
export function updateVectorset(owner, name, vectorset) { export function updateVectorset(owner, name, vectorset) {
let newVectorset = Setting.deepCopy(vectorset); const newVectorset = Setting.deepCopy(vectorset);
return fetch(`${Setting.ServerUrl}/api/update-vectorset?id=${owner}/${encodeURIComponent(name)}`, { return fetch(`${Setting.ServerUrl}/api/update-vectorset?id=${owner}/${encodeURIComponent(name)}`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
body: JSON.stringify(newVectorset), body: JSON.stringify(newVectorset),
}).then(res => res.json()); }).then(res => res.json());
} }
export function addVectorset(vectorset) { export function addVectorset(vectorset) {
let newVectorset = Setting.deepCopy(vectorset); const newVectorset = Setting.deepCopy(vectorset);
return fetch(`${Setting.ServerUrl}/api/add-vectorset`, { return fetch(`${Setting.ServerUrl}/api/add-vectorset`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
body: JSON.stringify(newVectorset), body: JSON.stringify(newVectorset),
}).then(res => res.json()); }).then(res => res.json());
} }
export function deleteVectorset(vectorset) { export function deleteVectorset(vectorset) {
let newVectorset = Setting.deepCopy(vectorset); const newVectorset = Setting.deepCopy(vectorset);
return fetch(`${Setting.ServerUrl}/api/delete-vectorset`, { return fetch(`${Setting.ServerUrl}/api/delete-vectorset`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
body: JSON.stringify(newVectorset), body: JSON.stringify(newVectorset),
}).then(res => res.json()); }).then(res => res.json());
} }

View File

@ -3,54 +3,54 @@ import * as Setting from "../Setting";
export function getGlobalVideos() { export function getGlobalVideos() {
return fetch(`${Setting.ServerUrl}/api/get-global-videos`, { return fetch(`${Setting.ServerUrl}/api/get-global-videos`, {
method: "GET", method: "GET",
credentials: "include" credentials: "include",
}).then(res => res.json()); }).then(res => res.json());
} }
export function getVideos(owner) { export function getVideos(owner) {
return fetch(`${Setting.ServerUrl}/api/get-videos?owner=${owner}`, { return fetch(`${Setting.ServerUrl}/api/get-videos?owner=${owner}`, {
method: "GET", method: "GET",
credentials: "include" credentials: "include",
}).then(res => res.json()); }).then(res => res.json());
} }
export function getVideo(owner, name) { export function getVideo(owner, name) {
return fetch(`${Setting.ServerUrl}/api/get-video?id=${owner}/${encodeURIComponent(name)}`, { return fetch(`${Setting.ServerUrl}/api/get-video?id=${owner}/${encodeURIComponent(name)}`, {
method: "GET", method: "GET",
credentials: "include" credentials: "include",
}).then(res => res.json()); }).then(res => res.json());
} }
export function getVideoGraph(owner, name, clusterNumber, distanceLimit) { export function getVideoGraph(owner, name, clusterNumber, distanceLimit) {
return fetch(`${Setting.ServerUrl}/api/get-video-graph?id=${owner}/${encodeURIComponent(name)}&clusterNumber=${clusterNumber}&distanceLimit=${distanceLimit}`, { return fetch(`${Setting.ServerUrl}/api/get-video-graph?id=${owner}/${encodeURIComponent(name)}&clusterNumber=${clusterNumber}&distanceLimit=${distanceLimit}`, {
method: "GET", method: "GET",
credentials: "include" credentials: "include",
}).then(res => res.json()); }).then(res => res.json());
} }
export function updateVideo(owner, name, video) { export function updateVideo(owner, name, video) {
let newVideo = Setting.deepCopy(video); const newVideo = Setting.deepCopy(video);
return fetch(`${Setting.ServerUrl}/api/update-video?id=${owner}/${encodeURIComponent(name)}`, { return fetch(`${Setting.ServerUrl}/api/update-video?id=${owner}/${encodeURIComponent(name)}`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
body: JSON.stringify(newVideo), body: JSON.stringify(newVideo),
}).then(res => res.json()); }).then(res => res.json());
} }
export function addVideo(video) { export function addVideo(video) {
let newVideo = Setting.deepCopy(video); const newVideo = Setting.deepCopy(video);
return fetch(`${Setting.ServerUrl}/api/add-video`, { return fetch(`${Setting.ServerUrl}/api/add-video`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
body: JSON.stringify(newVideo), body: JSON.stringify(newVideo),
}).then(res => res.json()); }).then(res => res.json());
} }
export function deleteVideo(video) { export function deleteVideo(video) {
let newVideo = Setting.deepCopy(video); const newVideo = Setting.deepCopy(video);
return fetch(`${Setting.ServerUrl}/api/delete-video`, { return fetch(`${Setting.ServerUrl}/api/delete-video`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
body: JSON.stringify(newVideo), body: JSON.stringify(newVideo),
}).then(res => res.json()); }).then(res => res.json());
} }

View File

@ -3,61 +3,61 @@ import * as Setting from "../Setting";
export function getGlobalWordsets() { export function getGlobalWordsets() {
return fetch(`${Setting.ServerUrl}/api/get-global-wordsets`, { return fetch(`${Setting.ServerUrl}/api/get-global-wordsets`, {
method: "GET", method: "GET",
credentials: "include" credentials: "include",
}).then(res => res.json()); }).then(res => res.json());
} }
export function getWordsets(owner) { export function getWordsets(owner) {
return fetch(`${Setting.ServerUrl}/api/get-wordsets?owner=${owner}`, { return fetch(`${Setting.ServerUrl}/api/get-wordsets?owner=${owner}`, {
method: "GET", method: "GET",
credentials: "include" credentials: "include",
}).then(res => res.json()); }).then(res => res.json());
} }
export function getWordset(owner, name) { export function getWordset(owner, name) {
return fetch(`${Setting.ServerUrl}/api/get-wordset?id=${owner}/${encodeURIComponent(name)}`, { return fetch(`${Setting.ServerUrl}/api/get-wordset?id=${owner}/${encodeURIComponent(name)}`, {
method: "GET", method: "GET",
credentials: "include" credentials: "include",
}).then(res => res.json()); }).then(res => res.json());
} }
export function getWordsetGraph(owner, name, clusterNumber, distanceLimit) { export function getWordsetGraph(owner, name, clusterNumber, distanceLimit) {
return fetch(`${Setting.ServerUrl}/api/get-wordset-graph?id=${owner}/${encodeURIComponent(name)}&clusterNumber=${clusterNumber}&distanceLimit=${distanceLimit}`, { return fetch(`${Setting.ServerUrl}/api/get-wordset-graph?id=${owner}/${encodeURIComponent(name)}&clusterNumber=${clusterNumber}&distanceLimit=${distanceLimit}`, {
method: "GET", method: "GET",
credentials: "include" credentials: "include",
}).then(res => res.json()); }).then(res => res.json());
} }
export function getWordsetMatch(owner, name) { export function getWordsetMatch(owner, name) {
return fetch(`${Setting.ServerUrl}/api/get-wordset-match?id=${owner}/${encodeURIComponent(name)}`, { return fetch(`${Setting.ServerUrl}/api/get-wordset-match?id=${owner}/${encodeURIComponent(name)}`, {
method: "GET", method: "GET",
credentials: "include" credentials: "include",
}).then(res => res.json()); }).then(res => res.json());
} }
export function updateWordset(owner, name, wordset) { export function updateWordset(owner, name, wordset) {
let newWordset = Setting.deepCopy(wordset); const newWordset = Setting.deepCopy(wordset);
return fetch(`${Setting.ServerUrl}/api/update-wordset?id=${owner}/${encodeURIComponent(name)}`, { return fetch(`${Setting.ServerUrl}/api/update-wordset?id=${owner}/${encodeURIComponent(name)}`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
body: JSON.stringify(newWordset), body: JSON.stringify(newWordset),
}).then(res => res.json()); }).then(res => res.json());
} }
export function addWordset(wordset) { export function addWordset(wordset) {
let newWordset = Setting.deepCopy(wordset); const newWordset = Setting.deepCopy(wordset);
return fetch(`${Setting.ServerUrl}/api/add-wordset`, { return fetch(`${Setting.ServerUrl}/api/add-wordset`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
body: JSON.stringify(newWordset), body: JSON.stringify(newWordset),
}).then(res => res.json()); }).then(res => res.json());
} }
export function deleteWordset(wordset) { export function deleteWordset(wordset) {
let newWordset = Setting.deepCopy(wordset); const newWordset = Setting.deepCopy(wordset);
return fetch(`${Setting.ServerUrl}/api/delete-wordset`, { return fetch(`${Setting.ServerUrl}/api/delete-wordset`, {
method: 'POST', method: "POST",
credentials: 'include', credentials: "include",
body: JSON.stringify(newWordset), body: JSON.stringify(newWordset),
}).then(res => res.json()); }).then(res => res.json());
} }

View File

@ -18,20 +18,20 @@ function initLanguage() {
let userLanguage; let userLanguage;
userLanguage = navigator.language; userLanguage = navigator.language;
switch (userLanguage) { switch (userLanguage) {
case "zh-CN": case "zh-CN":
language = "zh"; language = "zh";
break; break;
case "zh": case "zh":
language = "zh"; language = "zh";
break; break;
case "en": case "en":
language = "en"; language = "en";
break; break;
case "en-US": case "en-US":
language = "en"; language = "en";
break; break;
default: default:
language = Conf.DefaultLanguage; language = Conf.DefaultLanguage;
} }
} }
} }
@ -50,7 +50,7 @@ i18n.init({
interpolation: { interpolation: {
escapeValue: false, escapeValue: false,
}, },
//debug: true, // debug: true,
saveMissing: true, saveMissing: true,
}); });

View File

@ -2,25 +2,25 @@
// https://www.cnblogs.com/xuexia/p/12092768.html // https://www.cnblogs.com/xuexia/p/12092768.html
// react-app-polyfill // react-app-polyfill
// https://www.npmjs.com/package/react-app-polyfill // https://www.npmjs.com/package/react-app-polyfill
import 'react-app-polyfill/ie9'; import "react-app-polyfill/ie9";
import 'react-app-polyfill/stable'; import "react-app-polyfill/stable";
import React from 'react'; import React from "react";
import ReactDOM from 'react-dom'; import ReactDOM from "react-dom";
import './index.css'; import "./index.css";
import './font.css'; import "./font.css";
import App from './App'; import App from "./App";
import * as serviceWorker from './serviceWorker'; import * as serviceWorker from "./serviceWorker";
// import 'antd/dist/antd.min.css'; // import 'antd/dist/antd.min.css';
import {BrowserRouter} from 'react-router-dom'; import {BrowserRouter} from "react-router-dom";
import './i18n'; import "./i18n";
ReactDOM.render(( ReactDOM.render((
<BrowserRouter> <BrowserRouter>
<App/> <App />
</BrowserRouter> </BrowserRouter>
), ),
document.getElementById('root')); document.getElementById("root"));
// If you want your app to work offline and load faster, you can change // If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls. // unregister() to register() below. Note this comes with some pitfalls.

View File

@ -11,9 +11,9 @@
// opt-in, read https://bit.ly/CRA-PWA // opt-in, read https://bit.ly/CRA-PWA
const isLocalhost = Boolean( const isLocalhost = Boolean(
window.location.hostname === 'localhost' || window.location.hostname === "localhost" ||
// [::1] is the IPv6 localhost address. // [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' || window.location.hostname === "[::1]" ||
// 127.0.0.1/8 is considered localhost for IPv4. // 127.0.0.1/8 is considered localhost for IPv4.
window.location.hostname.match( window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
@ -21,7 +21,7 @@ const isLocalhost = Boolean(
); );
export function register(config) { export function register(config) {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) { if (process.env.NODE_ENV === "production" && "serviceWorker" in navigator) {
// The URL constructor is available in all browsers that support SW. // The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
if (publicUrl.origin !== window.location.origin) { if (publicUrl.origin !== window.location.origin) {
@ -31,7 +31,7 @@ export function register(config) {
return; return;
} }
window.addEventListener('load', () => { window.addEventListener("load", () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) { if (isLocalhost) {
@ -42,8 +42,8 @@ export function register(config) {
// service worker/PWA documentation. // service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => { navigator.serviceWorker.ready.then(() => {
console.log( console.log(
'This web app is being served cache-first by a service ' + "This web app is being served cache-first by a service " +
'worker. To learn more, visit https://bit.ly/CRA-PWA' "worker. To learn more, visit https://bit.ly/CRA-PWA"
); );
}); });
} else { } else {
@ -64,14 +64,14 @@ function registerValidSW(swUrl, config) {
return; return;
} }
installingWorker.onstatechange = () => { installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') { if (installingWorker.state === "installed") {
if (navigator.serviceWorker.controller) { if (navigator.serviceWorker.controller) {
// At this point, the updated precached content has been fetched, // At this point, the updated precached content has been fetched,
// but the previous service worker will still serve the older // but the previous service worker will still serve the older
// content until all client tabs are closed. // content until all client tabs are closed.
console.log( console.log(
'New content is available and will be used when all ' + "New content is available and will be used when all " +
'tabs for this page are closed. See https://bit.ly/CRA-PWA.' "tabs for this page are closed. See https://bit.ly/CRA-PWA."
); );
// Execute callback // Execute callback
@ -82,7 +82,7 @@ function registerValidSW(swUrl, config) {
// At this point, everything has been precached. // At this point, everything has been precached.
// It's the perfect time to display a // It's the perfect time to display a
// "Content is cached for offline use." message. // "Content is cached for offline use." message.
console.log('Content is cached for offline use.'); console.log("Content is cached for offline use.");
// Execute callback // Execute callback
if (config && config.onSuccess) { if (config && config.onSuccess) {
@ -94,7 +94,7 @@ function registerValidSW(swUrl, config) {
}; };
}) })
.catch(error => { .catch(error => {
console.error('Error during service worker registration:', error); console.error("Error during service worker registration:", error);
}); });
} }
@ -103,10 +103,10 @@ function checkValidServiceWorker(swUrl, config) {
fetch(swUrl) fetch(swUrl)
.then(response => { .then(response => {
// Ensure service worker exists, and that we really are getting a JS file. // Ensure service worker exists, and that we really are getting a JS file.
const contentType = response.headers.get('content-type'); const contentType = response.headers.get("content-type");
if ( if (
response.status === 404 || response.status === 404 ||
(contentType != null && contentType.indexOf('javascript') === -1) (contentType != null && contentType.indexOf("javascript") === -1)
) { ) {
// No service worker found. Probably a different app. Reload the page. // No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => { navigator.serviceWorker.ready.then(registration => {
@ -121,13 +121,13 @@ function checkValidServiceWorker(swUrl, config) {
}) })
.catch(() => { .catch(() => {
console.log( console.log(
'No internet connection found. App is running in offline mode.' "No internet connection found. App is running in offline mode."
); );
}); });
} }
export function unregister() { export function unregister() {
if ('serviceWorker' in navigator) { if ("serviceWorker" in navigator) {
navigator.serviceWorker.ready.then(registration => { navigator.serviceWorker.ready.then(registration => {
registration.unregister(); registration.unregister();
}); });