This commit is contained in:
Wallace Breza 2018-11-29 11:30:53 -08:00
Родитель 0eccc58e0a
Коммит f22d79f6cd
31 изменённых файлов: 531 добавлений и 491 удалений

4
package-lock.json сгенерированный
Просмотреть файл

@ -9928,7 +9928,7 @@
},
"pretty-bytes": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-1.0.4.tgz",
"resolved": "http://registry.npmjs.org/pretty-bytes/-/pretty-bytes-1.0.4.tgz",
"integrity": "sha1-CiLoIQYJrTVUL4yNXSFZr/B1HIQ=",
"requires": {
"get-stdin": "^4.0.1",
@ -12251,7 +12251,7 @@
},
"string_decoder": {
"version": "0.10.31",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
"resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
},
"through2": {

Просмотреть файл

@ -28,7 +28,7 @@
"scripts": {
"start": "nf start -p 3000",
"build": "react-scripts build",
"pretest": "./node_modules/.bin/tslint 'src/**/*.ts'",
"pretest": "./node_modules/.bin/tslint 'src/**/*.ts*'",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject",
"electron": "webpack --config ./config/webpack.dev.config.js && electron .",

Просмотреть файл

@ -1,19 +1,19 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { Provider } from 'react-redux';
import createReduxStore from './store/store';
import initialState from './store/initialState';
import ApplicationState from './store/applicationState';
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { Provider } from "react-redux";
import createReduxStore from "./store/store";
import initialState from "./store/initialState";
import ApplicationState from "./store/applicationState";
it('renders without crashing', () => {
const defaultState: ApplicationState = initialState;
const store = createReduxStore(defaultState);
const div = document.createElement('div');
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>, div);
ReactDOM.unmountComponentAtNode(div);
it("renders without crashing", () => {
const defaultState: ApplicationState = initialState;
const store = createReduxStore(defaultState);
const div = document.createElement("div");
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>, div);
ReactDOM.unmountComponentAtNode(div);
});

Просмотреть файл

@ -1,33 +1,33 @@
import React from 'react';
import { connect } from 'react-redux';
import { BrowserRouter as Router } from 'react-router-dom';
import Navbar from './components/shell/navbar';
import Sidebar from './components/shell/sidebar';
import MainContentRouter from './components/shell/mainContentRouter';
import ApplicationState, { IProject } from './store/applicationState';
import './App.scss';
import React from "react";
import { connect } from "react-redux";
import { BrowserRouter as Router } from "react-router-dom";
import Navbar from "./components/shell/navbar";
import Sidebar from "./components/shell/sidebar";
import MainContentRouter from "./components/shell/mainContentRouter";
import ApplicationState, { IProject } from "./store/applicationState";
import "./App.scss";
interface AppProps {
currentProject?: IProject
interface IAppProps {
currentProject?: IProject;
}
function mapStateToProps(state: ApplicationState) {
return {
currentProject: state.currentProject
currentProject: state.currentProject,
};
}
@connect(mapStateToProps)
class App extends React.Component<AppProps> {
class App extends React.Component<IAppProps> {
constructor(props, context) {
super(props, context);
this.state = {
currentProject: this.props.currentProject
currentProject: this.props.currentProject,
};
}
render() {
public render() {
return (
<Router>
<div className="app-shell">

Просмотреть файл

@ -6,8 +6,7 @@ const projectService = new ProjectService();
export default interface IProjectActions {
loadProjects(): Promise<IProject[]>;
loadProject(projectId: string): Promise<IProject>;
loadProject(project: IProject): Promise<IProject>;
loadProject(value: IProject | string): Promise<IProject>;
saveProject(project: IProject): Promise<IProject>;
deleteProject(project: IProject): Promise<void>;
closeProject();
@ -18,18 +17,17 @@ export function loadProject(value: string | IProject) {
try {
let project: IProject = value as IProject;
if (typeof (value) === 'string') {
if (typeof (value) === "string") {
project = await projectService.get(value);
}
dispatch({ type: ActionTypes.LOAD_PROJECT_SUCCESS, project: project });
dispatch({ type: ActionTypes.LOAD_PROJECT_SUCCESS, project });
return project;
}
catch (err) {
} catch (err) {
throw err;
}
}
};
}
export function loadProjects() {

Просмотреть файл

@ -1,17 +1,17 @@
import React, { SyntheticEvent } from 'react'
import './condensedList.scss';
import { Link } from 'react-router-dom';
import React, { SyntheticEvent } from "react";
import "./condensedList.scss";
import { Link } from "react-router-dom";
interface CondensedListProps {
title: string,
items: any[],
newLinkTo?: string,
interface ICondensedListProps {
title: string;
items: any[];
newLinkTo?: string;
onClick?: (item) => void;
onDelete?: (item) => void;
Component: any
Component: any;
}
export default class CondensedList extends React.Component<CondensedListProps> {
export default class CondensedList extends React.Component<ICondensedListProps> {
constructor(props, context) {
super(props, context);
@ -19,22 +19,7 @@ export default class CondensedList extends React.Component<CondensedListProps> {
this.onItemDelete = this.onItemDelete.bind(this);
}
onItemClick = (e, item) => {
if (this.props.onClick) {
this.props.onClick(item);
}
}
onItemDelete = (e: SyntheticEvent, item) => {
e.stopPropagation();
e.preventDefault();
if (this.props.onDelete) {
this.props.onDelete(item);
}
}
render() {
public render() {
const { title, items, newLinkTo, Component } = this.props;
return (
@ -57,10 +42,28 @@ export default class CondensedList extends React.Component<CondensedListProps> {
}
{(items && items.length > 0) &&
<ul className="condensed-list-items">
{items.map(item => <Component key={item.id} item={item} onClick={(e) => this.onItemClick(e, item)} onDelete={(e) => this.onItemDelete(e, item)} />)}
{items.map((item) => <Component key={item.id}
item={item}
onClick={(e) => this.onItemClick(e, item)}
onDelete={(e) => this.onItemDelete(e, item)} />)}
</ul>
}
</div>
);
}
}
private onItemClick = (e, item) => {
if (this.props.onClick) {
this.props.onClick(item);
}
}
private onItemDelete = (e: SyntheticEvent, item) => {
e.stopPropagation();
e.preventDefault();
if (this.props.onDelete) {
this.props.onDelete(item);
}
}
}

Просмотреть файл

@ -1,10 +1,10 @@
import React from 'react';
import { NavLink } from 'react-router-dom';
import React from "react";
import { NavLink } from "react-router-dom";
export default function ConditionalNavLink({ to, disabled, ...props }) {
if (disabled) {
return (<span className="disabled" title={props.title} >{props.children}</span>)
return (<span className="disabled" title={props.title} >{props.children}</span>);
} else {
return (<NavLink title={props.title} to={to}>{props.children}</NavLink>)
return (<NavLink title={props.title} to={to}>{props.children}</NavLink>);
}
}
}

Просмотреть файл

@ -1,24 +1,24 @@
import React from 'react';
import ConnectionPicker from './connectionPicker';
import { BrowserRouter as Router } from 'react-router-dom';
import { mount } from 'enzyme';
import { IConnection } from '../../store/applicationState';
import React from "react";
import ConnectionPicker from "./connectionPicker";
import { BrowserRouter as Router } from "react-router-dom";
import { mount } from "enzyme";
import { IConnection } from "../../store/applicationState";
describe('Connection Picker Component', () => {
describe("Connection Picker Component", () => {
let wrapper: any = null;
let connections: IConnection[] = null;
let onChangeHandler: (value: any) => void;;
let onChangeHandler: (value: any) => void;
beforeEach(() => {
connections = [
{ id: '1', 'name': 'Connection 1', providerType: 'localFileSystemProxy', providerOptions: null },
{ id: '2', 'name': 'Connection 2', providerType: 'localFileSystemProxy', providerOptions: null },
{ id: '3', 'name': 'Connection 3', providerType: 'localFileSystemProxy', providerOptions: null },
{ id: '4', 'name': 'Connection 4', providerType: 'localFileSystemProxy', providerOptions: null },
{ id: "1", name: "Connection 1", providerType: "localFileSystemProxy", providerOptions: null },
{ id: "2", name: "Connection 2", providerType: "localFileSystemProxy", providerOptions: null },
{ id: "3", name: "Connection 3", providerType: "localFileSystemProxy", providerOptions: null },
{ id: "4", name: "Connection 4", providerType: "localFileSystemProxy", providerOptions: null },
];
const options = {
connections: connections
connections,
};
onChangeHandler = jest.fn();
@ -26,30 +26,30 @@ describe('Connection Picker Component', () => {
wrapper = mount(
<Router>
<ConnectionPicker
value={null}
value=""
options={options}
onChange={onChangeHandler}
/>
</Router>
</Router>,
);
});
it('renders a default "Select Connection" option', () => {
const firstOption = wrapper.find('option').first();
expect(firstOption.text()).toEqual('Select Connection');
it("renders a default 'Select Connection' option", () => {
const firstOption = wrapper.find("option").first();
expect(firstOption.text()).toEqual("Select Connection");
});
it('renders options from connection props', () => {
it("renders options from connection props", () => {
expect(wrapper).not.toBeNull();
const optionElements = wrapper.find('option');
const optionElements = wrapper.find("option");
expect(optionElements.length).toEqual(connections.length + 1);
expect(wrapper.prop('value')).not.toBeDefined();
expect(wrapper.prop("value")).not.toBeDefined();
});
it('raises onChange event when dropdown is modified', () => {
const connectionId = '2';
it("raises onChange event when dropdown is modified", () => {
const connectionId = "2";
wrapper.find('select').simulate('change', { target: { value: connectionId } });
wrapper.find("select").simulate("change", { target: { value: connectionId } });
expect(onChangeHandler).toBeCalledWith(connectionId);
});
});

Просмотреть файл

@ -1,50 +1,40 @@
import React from 'react';
import { IConnection } from '../../store/applicationState';
import { Link } from 'react-router-dom';
import React from "react";
import { IConnection } from "../../store/applicationState";
import { Link } from "react-router-dom";
interface ConnectionPickerProps {
interface IConnectionPickerProps {
id?: string;
value: any;
onChange: (value) => void;
options?: {
connections: IConnection[]
}
connections: IConnection[],
};
}
interface ConnectionPickerState {
interface IConnectionPickerState {
value: any;
}
export default class ConnectionPicker extends React.Component<ConnectionPickerProps, ConnectionPickerState> {
export default class ConnectionPicker extends React.Component<IConnectionPickerProps, IConnectionPickerState> {
constructor(props, context) {
super(props, context);
this.state = {
value: this.props.value
value: this.props.value,
};
this.onChange = this.onChange.bind(this);
}
onChange = (e) => {
const selectedConnection = this.props.options.connections.find(connection => connection.id === e.target.value);
if (selectedConnection) {
this.setState({
value: selectedConnection.id
}, () => this.props.onChange(selectedConnection.id));
}
}
componentDidUpdate(prevProps) {
public componentDidUpdate(prevProps) {
if (prevProps.value !== this.props.value) {
this.setState({
value: this.props.value
value: this.props.value || "",
});
}
}
render() {
let { id, options } = this.props;
public render() {
const { id, options } = this.props;
const connections = options.connections || [];
const { value } = this.state;
@ -53,7 +43,9 @@ export default class ConnectionPicker extends React.Component<ConnectionPickerPr
<div className="input-group">
<select id={id} value={value} onChange={this.onChange} className="form-control">
<option value="">Select Connection</option>
{connections.map(connection => <option key={connection.id} value={connection.id}>{connection.name}</option>)}
{connections.map((connection) =>
<option key={connection.id} value={connection.id}>{connection.name}</option>)
}
</select>
<div className="input-group-append">
<Link to="/connections/create" className="btn btn-primary" type="button">Add Connection</Link>
@ -61,4 +53,15 @@ export default class ConnectionPicker extends React.Component<ConnectionPickerPr
</div>
);
}
}
private onChange = (e) => {
const selectedConnection = this.props.options.connections
.find((connection) => connection.id === e.target.value);
if (selectedConnection) {
this.setState({
value: selectedConnection.id,
}, () => this.props.onChange(selectedConnection.id));
}
}
}

Просмотреть файл

@ -1,14 +1,14 @@
import React, { SyntheticEvent } from 'react';
import shortid from 'shortid';
import HtmlFileReader from '../../common/htmlFileReader';
import React, { SyntheticEvent } from "react";
import shortid from "shortid";
import HtmlFileReader from "../../common/htmlFileReader";
interface FilePickerProps {
ref: React.RefObject<FilePicker>,
interface IFilePickerProps {
ref: React.RefObject<FilePicker>;
onChange: (sender: SyntheticEvent, fileText: string | ArrayBuffer) => void;
onError: (sender: SyntheticEvent, error: any) => void;
}
export default class FilePicker extends React.Component<FilePickerProps> {
export default class FilePicker extends React.Component<IFilePickerProps> {
private fileInput;
constructor(props, context) {
@ -18,24 +18,24 @@ export default class FilePicker extends React.Component<FilePickerProps> {
this.onFileUploaded = this.onFileUploaded.bind(this);
}
onFileUploaded = (e) => {
if (e.currentTarget.files.length === 0) {
this.props.onError(e, 'No files were selected');
}
const reader = new HtmlFileReader();
reader.readAsText(e.currentTarget.files[0])
.then(fileText => this.props.onChange(e, fileText))
.catch(err => this.props.onError(e, err));
}
upload = () => {
public upload = () => {
this.fileInput.current.click();
}
render() {
public render() {
return (
<input id={shortid.generate()} ref={this.fileInput} type="file" onChange={this.onFileUploaded} />
);
}
}
private onFileUploaded = (e) => {
if (e.currentTarget.files.length === 0) {
this.props.onError(e, "No files were selected");
}
const reader = new HtmlFileReader();
reader.readAsText(e.currentTarget.files[0])
.then((fileText) => this.props.onChange(e, fileText))
.catch((err) => this.props.onError(e, err));
}
}

Просмотреть файл

@ -1,40 +1,31 @@
import React from 'react';
import { LocalFileSystemProxy } from '../../providers/storage/localFileSystemProxy';
import React from "react";
import { LocalFileSystemProxy } from "../../providers/storage/localFileSystemProxy";
interface LocalFolderPickerProps {
interface ILocalFolderPickerProps {
id: string;
value: any;
onChange: (value) => void;
}
interface LocalFolderPickerState {
value: any
interface ILocalFolderPickerState {
value: any;
}
export default class LocalFolderPicker extends React.Component<LocalFolderPickerProps, LocalFolderPickerState> {
export default class LocalFolderPicker extends React.Component<ILocalFolderPickerProps, ILocalFolderPickerState> {
private localFileSystem: LocalFileSystemProxy;
constructor(props, context) {
super(props, context);
this.state = {
value: this.props.value
value: this.props.value,
};
this.localFileSystem = new LocalFileSystemProxy();
this.selectLocalFolder = this.selectLocalFolder.bind(this);
}
selectLocalFolder = async () => {
const filePath = await this.localFileSystem.selectContainer();
if (filePath) {
this.setState({
value: filePath
}, () => this.props.onChange(filePath));
}
}
render() {
public render() {
const { id } = this.props;
const { value } = this.state;
@ -42,9 +33,21 @@ export default class LocalFolderPicker extends React.Component<LocalFolderPicker
<div className="input-group">
<input id={id} type="text" className="form-control" value={value} readOnly />
<div className="input-group-append">
<button className="btn btn-primary" type="button" onClick={this.selectLocalFolder}>Select Folder</button>
<button className="btn btn-primary"
type="button"
onClick={this.selectLocalFolder}>Select Folder
</button>
</div>
</div>
);
}
}
private selectLocalFolder = async () => {
const filePath = await this.localFileSystem.selectContainer();
if (filePath) {
this.setState({
value: filePath,
}, () => this.props.onChange(filePath));
}
}
}

Просмотреть файл

@ -1,14 +1,13 @@
import React from 'react'
import './tagsInput.scss'
import { WithContext as ReactTags } from 'react-tag-input';
import React from "react";
import "./tagsInput.scss";
import { WithContext as ReactTags } from "react-tag-input";
interface TagsInputProps {
interface ITagsInputProps {
tags: any;
onChange: (value) => void;
}
interface TagsInputState {
interface ITagsInputState {
tags: any;
}
@ -19,36 +18,49 @@ const KeyCodes = {
const delimiters = [KeyCodes.comma, KeyCodes.enter];
export default class TagsInput extends React.Component<TagsInputProps, TagsInputState> {
export default class TagsInput extends React.Component<ITagsInputProps, ITagsInputState> {
constructor(props) {
super(props);
this.state = {
tags: []
}
tags: [],
};
this.handleDelete = this.handleDelete.bind(this);
this.handleAddition = this.handleAddition.bind(this);
this.handleDrag = this.handleDrag.bind(this);
}
convertToFlatList(tags) {
return tags.map(element => element.text).join();
public render() {
const { tags } = this.state;
return (
<div>
<ReactTags tags={tags}
handleDelete={this.handleDelete}
handleAddition={this.handleAddition}
handleDrag={this.handleDrag}
delimiters={delimiters} />
</div>
);
}
handleAddition = (tag) => {
private convertToFlatList(tags) {
return tags.map((element) => element.text).join();
}
private handleAddition = (tag) => {
this.setState({
tags: [...this.state.tags, tag]
tags: [...this.state.tags, tag],
}, () => this.props.onChange(this.convertToFlatList(this.state.tags)));
}
handleDelete = (i) => {
private handleDelete = (i) => {
const { tags } = this.state;
this.setState({
tags: tags.filter((tag, index) => index !== i),
}, () => this.props.onChange(this.convertToFlatList(this.state.tags)));
}
handleDrag = (tag, currPos, newPos) => {
private handleDrag = (tag, currPos, newPos) => {
const tags = [...this.state.tags];
const newTags = tags.slice();
@ -59,18 +71,4 @@ export default class TagsInput extends React.Component<TagsInputProps, TagsInput
this.setState({ tags: newTags },
() => this.props.onChange(this.convertToFlatList(this.state.tags)));
}
render() {
const { tags } = this.state;
return (
<div>
<ReactTags tags={tags}
handleDelete={this.handleDelete}
handleAddition={this.handleAddition}
handleDrag={this.handleDrag}
delimiters={delimiters} />
</div>
)
}
}
}

Просмотреть файл

@ -1,7 +1,7 @@
import React from 'react';
import React from "react";
export default class ActiveLearningPage extends React.Component {
render() {
public render() {
return (
<div>ActiveLearningPage</div>
);

Просмотреть файл

@ -1,41 +1,41 @@
import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import IApplicationActions, * as applicationActions from '../../../actions/applicationActions';
import ApplicationState, { IAppSettings } from '../../../store/applicationState';
import Form from 'react-jsonschema-form'
import formSchema from './appSettings.json';
import uiSchema from './appSettings.ui.json';
import './appSettings.scss';
import ConnectionPicker from '../../common/connectionPicker';
import React from "react";
import { connect } from "react-redux";
import { bindActionCreators } from "redux";
import IApplicationActions, * as applicationActions from "../../../actions/applicationActions";
import ApplicationState, { IAppSettings } from "../../../store/applicationState";
import Form from "react-jsonschema-form";
import formSchema from "./appSettings.json";
import uiSchema from "./appSettings.ui.json";
import "./appSettings.scss";
import ConnectionPicker from "../../common/connectionPicker";
interface IAppSettingsProps {
appSettings: IAppSettings,
actions: IApplicationActions
appSettings: IAppSettings;
actions: IApplicationActions;
}
interface IAppSettingsState {
formSchema: any,
uiSchema: any,
appSettings: IAppSettings
formSchema: any;
uiSchema: any;
appSettings: IAppSettings;
}
function mapStateToProps(state: ApplicationState) {
return {
appSettings: state.appSettings
appSettings: state.appSettings,
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(applicationActions, dispatch)
actions: bindActionCreators(applicationActions, dispatch),
};
}
@connect(mapStateToProps, mapDispatchToProps)
export default class AppSettingsPage extends React.Component<IAppSettingsProps, IAppSettingsState> {
private widgets: any = {
connectionPicker: ConnectionPicker
connectionPicker: ConnectionPicker,
};
constructor(props: IAppSettingsProps) {
@ -44,7 +44,7 @@ export default class AppSettingsPage extends React.Component<IAppSettingsProps,
this.state = {
formSchema: { ...formSchema },
uiSchema: { ...uiSchema },
appSettings: { ...this.props.appSettings }
appSettings: { ...this.props.appSettings },
};
this.toggleDevTools = this.toggleDevTools.bind(this);
@ -52,19 +52,7 @@ export default class AppSettingsPage extends React.Component<IAppSettingsProps,
this.onFormSubmit = this.onFormSubmit.bind(this);
}
onFormSubmit = (form) => {
}
toggleDevTools = () => {
this.props.actions.toggleDevTools(!this.props.appSettings.devToolsEnabled);
}
reloadApp = () => {
this.props.actions.reloadApplication();
}
render() {
public render() {
return (
<div className="m-3 text-light">
<h3><i className="fas fa-cog fa-1x"></i><span className="px-2">Application Settings</span></h3>
@ -81,15 +69,31 @@ export default class AppSettingsPage extends React.Component<IAppSettingsProps,
<div className="app-settings-page-sidebar px-2">
<div className="my-3">
<p>Open application developer tools to help diagnose issues</p>
<button className="btn btn-primary btn-sm" onClick={this.toggleDevTools}>Toggle Developer Tools</button>
<button className="btn btn-primary btn-sm"
onClick={this.toggleDevTools}>Toggle Developer Tools
</button>
</div>
<div className="my-3">
<p>Reload the app discarding all current changes</p>
<button className="btn btn-primary btn-sm" onClick={this.reloadApp}>Refresh Application</button>
<button className="btn btn-primary btn-sm"
onClick={this.reloadApp}>Refresh Application
</button>
</div>
</div>
</div>
</div>
);
}
}
private onFormSubmit = (form) => {
// TODO: Submit form
}
private toggleDevTools = () => {
this.props.actions.toggleDevTools(!this.props.appSettings.devToolsEnabled);
}
private reloadApp = () => {
this.props.actions.reloadApplication();
}
}

Просмотреть файл

@ -1,26 +1,26 @@
import React from 'react';
import formSchema from './connectionForm.json';
import uiSchema from './connectionForm.ui.json';
import Form from 'react-jsonschema-form'
import { IConnection } from '../../../store/applicationState.js';
import LocalFolderPicker from '../../common/localFolderPicker';
import React from "react";
import formSchema from "./connectionForm.json";
import uiSchema from "./connectionForm.ui.json";
import Form from "react-jsonschema-form";
import { IConnection } from "../../../store/applicationState.js";
import LocalFolderPicker from "../../common/localFolderPicker";
interface ConnectionFormProps extends React.Props<ConnectionForm> {
interface IConnectionFormProps extends React.Props<ConnectionForm> {
connection: IConnection;
onSubmit: (connection: IConnection) => void;
}
interface ConnectionFormState {
interface IConnectionFormState {
providerName: string;
formSchema: any;
uiSchema: any;
formData: IConnection;
}
export default class ConnectionForm extends React.Component<ConnectionFormProps, ConnectionFormState> {
export default class ConnectionForm extends React.Component<IConnectionFormProps, IConnectionFormState> {
private widgets = {
LocalFolderPicker: LocalFolderPicker
}
localFolderPicker: LocalFolderPicker,
};
constructor(props, context) {
super(props, context);
@ -29,51 +29,13 @@ export default class ConnectionForm extends React.Component<ConnectionFormProps,
formSchema: { ...formSchema },
uiSchema: { ...uiSchema },
providerName: null,
formData: this.props.connection
formData: this.props.connection,
};
this.onFormChange = this.onFormChange.bind(this);
}
componentDidUpdate(prevProps) {
if (this.props.connection && prevProps.connection !== this.props.connection) {
this.bindForm(this.props.connection);
}
}
onFormChange = (args) => {
const providerType = args.formData.providerType;
if (providerType !== this.state.providerName) {
this.bindForm(args.formData, true);
}
};
private bindForm(connection: IConnection, resetProviderOptions: boolean = false) {
const providerType = connection.providerType;
const providerSchema = require(`../../../providers/storage/${providerType}.json`);
const providerUiSchema = require(`../../../providers/storage/${providerType}.ui.json`);
const formSchema = { ...this.state.formSchema };
formSchema.properties['providerOptions'] = providerSchema;
const uiSchema = { ...this.state.uiSchema };
uiSchema['providerOptions'] = providerUiSchema;
const formData = { ...connection };
if (resetProviderOptions) {
formData.providerOptions = {};
}
this.setState({
providerName: providerType,
formSchema: formSchema,
uiSchema: uiSchema,
formData: formData
});
}
render() {
public render() {
return (
<div className="app-connections-page-detail m-3 text-light">
<h3><i className="fas fa-plug fa-1x"></i><span className="px-2">Connection Settings</span></h3>
@ -90,4 +52,42 @@ export default class ConnectionForm extends React.Component<ConnectionFormProps,
);
}
}
public componentDidUpdate(prevProps) {
if (this.props.connection && prevProps.connection !== this.props.connection) {
this.bindForm(this.props.connection);
}
}
private onFormChange = (args) => {
const providerType = args.formData.providerType;
if (providerType !== this.state.providerName) {
this.bindForm(args.formData, true);
}
}
private bindForm(connection: IConnection, resetProviderOptions: boolean = false) {
const providerType = connection.providerType;
const providerSchema = require(`../../../providers/storage/${providerType}.json`);
const providerUiSchema = require(`../../../providers/storage/${providerType}.ui.json`);
const formSchema = { ...this.state.formSchema };
formSchema.properties["providerOptions"] = providerSchema;
const uiSchema = { ...this.state.uiSchema };
uiSchema["providerOptions"] = providerUiSchema;
const formData = { ...connection };
if (resetProviderOptions) {
formData.providerOptions = {};
}
this.setState({
providerName: providerType,
formSchema,
uiSchema,
formData,
});
}
}

Просмотреть файл

@ -1,5 +1,5 @@
import React from 'react';
import { NavLink } from 'react-router-dom';
import React from "react";
import { NavLink } from "react-router-dom";
export default function ConnectionItem({ item, onClick, onDelete }) {
return (
@ -11,4 +11,4 @@ export default function ConnectionItem({ item, onClick, onDelete }) {
</NavLink>
</li>
);
}
}

Просмотреть файл

@ -1,14 +1,14 @@
import React from 'react';
import { Route } from 'react-router-dom';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import ConnectionItem from './connectionItem';
import CondensedList from '../../common/condensedList';
import ApplicationState, { IConnection } from '../../../store/applicationState.js';
import { RouteComponentProps } from 'react-router-dom';
import IConnectionActions, * as connectionActions from '../../../actions/connectionActions';
import ConnectionForm from './connectionForm';
import './connectionsPage.scss';
import React from "react";
import { Route } from "react-router-dom";
import { connect } from "react-redux";
import { bindActionCreators } from "redux";
import ConnectionItem from "./connectionItem";
import CondensedList from "../../common/condensedList";
import ApplicationState, { IConnection } from "../../../store/applicationState.js";
import { RouteComponentProps } from "react-router-dom";
import IConnectionActions, * as connectionActions from "../../../actions/connectionActions";
import ConnectionForm from "./connectionForm";
import "./connectionsPage.scss";
export interface IConnectionPageProps extends RouteComponentProps, React.Props<ConnectionPage> {
connections: IConnection[];
@ -16,18 +16,18 @@ export interface IConnectionPageProps extends RouteComponentProps, React.Props<C
}
export interface IConnectionPageState {
connection: IConnection
connection: IConnection;
}
function mapStateToProps(state: ApplicationState) {
return {
connections: state.connections
connections: state.connections,
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(connectionActions, dispatch)
actions: bindActionCreators(connectionActions, dispatch),
};
}
@ -37,62 +37,38 @@ export default class ConnectionPage extends React.Component<IConnectionPageProps
super(props, context);
this.state = {
connection: null
connection: null,
};
this.onFormSubmit = this.onFormSubmit.bind(this);
this.onConnectionDelete = this.onConnectionDelete.bind(this);
}
componentDidMount() {
public componentDidMount() {
this.props.actions.loadConnections();
const connectionId = this.props.match.params['connectionId'];
const connectionId = this.props.match.params["connectionId"];
if (connectionId) {
this.loadConnection(connectionId);
}
}
loadConnection(connectionId: string) {
this.props.actions.loadConnection(connectionId)
.then(connection => {
this.setState({
connection: connection
});
})
.catch(() => {
this.setState({
connection: null
});
});
}
onConnectionDelete = (connection: IConnection) => {
this.props.actions.deleteConnection(connection);
}
componentDidUpdate = (prevProps) => {
const prevConnectionId = prevProps.match.params['connectionId'];
const newConnectionId = this.props.match.params['connectionId'];
public componentDidUpdate = (prevProps) => {
const prevConnectionId = prevProps.match.params["connectionId"];
const newConnectionId = this.props.match.params["connectionId"];
if (prevConnectionId !== newConnectionId) {
this.loadConnection(newConnectionId);
}
}
onFormSubmit = (connection: IConnection) => {
this.props.actions.saveConnection(connection).then(() => {
this.props.history.push('/connections');
});
}
render() {
public render() {
return (
<div className="app-connections-page">
<div className="app-connections-page-list bg-lighter-1">
<CondensedList
title="Connections"
newLinkTo={'/connections/create'}
newLinkTo={"/connections/create"}
onDelete={this.onConnectionDelete}
Component={ConnectionItem}
items={this.props.connections} />
@ -112,4 +88,28 @@ export default class ConnectionPage extends React.Component<IConnectionPageProps
</div>
);
}
private loadConnection(connectionId: string) {
this.props.actions.loadConnection(connectionId)
.then((connection) => {
this.setState({
connection,
});
})
.catch(() => {
this.setState({
connection: null,
});
});
}
private onConnectionDelete = (connection: IConnection) => {
this.props.actions.deleteConnection(connection);
}
private onFormSubmit = (connection: IConnection) => {
this.props.actions.saveConnection(connection).then(() => {
this.props.history.push("/connections");
});
}
}

Просмотреть файл

@ -1,7 +1,7 @@
import React from 'react';
import React from "react";
export default class EditorPage extends React.Component {
render() {
public render() {
return (
<div>EditorPage</div>
);

Просмотреть файл

@ -1,7 +1,7 @@
import React from 'react';
import React from "react";
export default class ExportPage extends React.Component {
render() {
public render() {
return (
<div>ExportPage</div>
);

Просмотреть файл

@ -1,36 +1,36 @@
import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import './homePage.scss';
import IProjectActions, * as projectActions from '../../../actions/projectActions';
import ApplicationState, { IProject } from '../../../store/applicationState';
import CondensedList from '../../common/condensedList';
import RecentProjectItem from './recentProjectItem';
import FilePicker from '../../common/filePicker';
import { Link, RouteComponentProps } from 'react-router-dom';
import React from "react";
import { connect } from "react-redux";
import { bindActionCreators } from "redux";
import "./homePage.scss";
import IProjectActions, * as projectActions from "../../../actions/projectActions";
import ApplicationState, { IProject } from "../../../store/applicationState";
import CondensedList from "../../common/condensedList";
import RecentProjectItem from "./recentProjectItem";
import FilePicker from "../../common/filePicker";
import { Link, RouteComponentProps } from "react-router-dom";
interface HomepageProps extends RouteComponentProps, React.Props<HomePage> {
recentProjects: IProject[],
actions: IProjectActions
interface IHomepageProps extends RouteComponentProps, React.Props<HomePage> {
recentProjects: IProject[];
actions: IProjectActions;
}
function mapStateToProps(state: ApplicationState) {
return {
recentProjects: state.recentProjects
recentProjects: state.recentProjects,
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(projectActions, dispatch)
actions: bindActionCreators(projectActions, dispatch),
};
}
@connect(mapStateToProps, mapDispatchToProps)
export default class HomePage extends React.Component<HomepageProps> {
export default class HomePage extends React.Component<IHomepageProps> {
private filePicker: React.RefObject<FilePicker>;
constructor(props: HomepageProps, context) {
constructor(props: IHomepageProps, context) {
super(props, context);
this.filePicker = React.createRef<FilePicker>();
@ -42,25 +42,7 @@ export default class HomePage extends React.Component<HomepageProps> {
this.props.actions.loadProjects();
}
onProjectFileUpload = (e, projectJson) => {
const project: IProject = JSON.parse(projectJson);
this.loadSelectedProject(project);
}
onProjectFileUploadError = (e, err) => {
console.error(err);
}
loadSelectedProject = (project: IProject) => {
this.props.actions.loadProject(project)
this.props.history.push(`/projects/${project.id}/settings`);
}
deleteProject = (project: IProject) => {
this.props.actions.deleteProject(project);
}
render() {
public render() {
return (
<div className="app-homepage">
<div className="app-homepage-main">
@ -95,4 +77,23 @@ export default class HomePage extends React.Component<HomepageProps> {
</div>
);
}
private onProjectFileUpload = (e, projectJson) => {
const project: IProject = JSON.parse(projectJson);
this.loadSelectedProject(project);
}
private onProjectFileUploadError = (e, err) => {
console.error(err);
}
private loadSelectedProject = (project: IProject) => {
this.props.actions.loadProject(project).then(() => {
this.props.history.push(`/projects/${project.id}/settings`);
});
}
private deleteProject = (project: IProject) => {
this.props.actions.deleteProject(project);
}
}

Просмотреть файл

@ -1,4 +1,4 @@
import React from 'react';
import React from "react";
export default function RecentProjectItem({ item, onClick, onDelete }) {
return (
@ -10,4 +10,4 @@ export default function RecentProjectItem({ item, onClick, onDelete }) {
</a>
</li>
);
}
}

Просмотреть файл

@ -1,7 +1,7 @@
import React from 'react';
import React from "react";
export default class ProfileSettingsPage extends React.Component {
render() {
public render() {
return (
<div>ProfileSettingsPage</div>
);

Просмотреть файл

@ -1,60 +1,60 @@
import React from 'react';
import Form from 'react-jsonschema-form'
import deepmerge from 'deepmerge';
import formSchema from './projectForm.json';
import uiSchema from './projectForm.ui.json';
import TagsInput from '../../common/tagsInput'
import ConnectionPicker from '../../common/connectionPicker';
import { IProject, IConnection } from '../../../store/applicationState.js';
import React from "react";
import Form from "react-jsonschema-form";
import deepmerge from "deepmerge";
import formSchema from "./projectForm.json";
import uiSchema from "./projectForm.ui.json";
import TagsInput from "../../common/tagsInput";
import ConnectionPicker from "../../common/connectionPicker";
import { IProject, IConnection } from "../../../store/applicationState.js";
interface ProjectFormProps extends React.Props<ProjectForm> {
interface IProjectFormProps extends React.Props<ProjectForm> {
project: IProject;
connections: IConnection[];
onSubmit: (project: IProject) => void;
}
interface ProjectFormState {
interface IProjectFormState {
formSchema: any;
uiSchema: any;
}
export default class ProjectForm extends React.Component<ProjectFormProps, ProjectFormState>{
export default class ProjectForm extends React.Component<IProjectFormProps, IProjectFormState> {
private widgets = {
connectionPicker: ConnectionPicker,
tagsInput: TagsInput
}
tagsInput: TagsInput,
};
constructor(props, context) {
super(props, context);
this.state = {
uiSchema: { ...uiSchema },
formSchema: { ...formSchema }
formSchema: { ...formSchema },
};
}
componentDidUpdate(prevProps) {
public componentDidUpdate(prevProps) {
if (prevProps.connections !== this.props.connections) {
const overrideUiSchema = {
sourceConnectionId: {
'ui:options': {
connections: this.props.connections
}
"ui:options": {
connections: this.props.connections,
},
},
targetConnectionId: {
'ui:options': {
connections: this.props.connections
}
}
"ui:options": {
connections: this.props.connections,
},
},
};
this.setState({
uiSchema: deepmerge(uiSchema, overrideUiSchema)
uiSchema: deepmerge(uiSchema, overrideUiSchema),
});
}
}
render() {
public render() {
return (
<Form
widgets={this.widgets}
@ -63,6 +63,6 @@ export default class ProjectForm extends React.Component<ProjectFormProps, Proje
formData={this.props.project}
onSubmit={this.props.onSubmit}>
</Form>
)
);
}
}
}

Просмотреть файл

@ -1,56 +1,56 @@
import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { RouteComponentProps } from 'react-router-dom';
import ProjectForm from './projectForm'
import IProjectActions, * as projectActions from '../../../actions/projectActions';
import ApplicationState, { IProject, IConnection } from '../../../store/applicationState';
import IConnectionActions, * as connectionActions from '../../../actions/connectionActions';
import React from "react";
import { connect } from "react-redux";
import { bindActionCreators } from "redux";
import { RouteComponentProps } from "react-router-dom";
import ProjectForm from "./projectForm";
import IProjectActions, * as projectActions from "../../../actions/projectActions";
import ApplicationState, { IProject, IConnection } from "../../../store/applicationState";
import IConnectionActions, * as connectionActions from "../../../actions/connectionActions";
interface ProjectSettingsPageProps extends RouteComponentProps, React.Props<ProjectSettingsPage> {
interface IProjectSettingsPageProps extends RouteComponentProps, React.Props<ProjectSettingsPage> {
currentProject: IProject;
projectActions: IProjectActions;
connectionActions: IConnectionActions;
connections: IConnection[];
}
interface ProjectSettingsPageState {
interface IProjectSettingsPageState {
project: IProject;
}
function mapStateToProps(state: ApplicationState) {
return {
currentProject: state.currentProject,
connections: state.connections
connections: state.connections,
};
}
function mapDispatchToProps(dispatch) {
return {
projectActions: bindActionCreators(projectActions, dispatch),
connectionActions: bindActionCreators(connectionActions, dispatch)
connectionActions: bindActionCreators(connectionActions, dispatch),
};
}
@connect(mapStateToProps, mapDispatchToProps)
export default class ProjectSettingsPage extends React.Component<ProjectSettingsPageProps, ProjectSettingsPageState> {
export default class ProjectSettingsPage extends React.Component<IProjectSettingsPageProps, IProjectSettingsPageState> {
constructor(props, context) {
super(props, context);
this.state = {
project: this.props.currentProject
project: this.props.currentProject,
};
this.onFormSubmit = this.onFormSubmit.bind(this);
}
async componentDidMount() {
const projectId = this.props.match.params['projectId'];
public async componentDidMount() {
const projectId = this.props.match.params["projectId"];
if (!this.state.project && projectId) {
const currentProject = await this.props.projectActions.loadProject(projectId);
this.setState({
project: currentProject
project: currentProject,
});
}
@ -59,22 +59,7 @@ export default class ProjectSettingsPage extends React.Component<ProjectSettings
}
}
onFormSubmit = (form) => {
this.setState({
project: {
...form.formData,
sourceConnection: this.props.connections.find(connection => connection.id === form.formData.sourceConnectionId),
targetConnection: this.props.connections.find(connection => connection.id === form.formData.targetConnectionId)
}
}, () => {
this.props.projectActions.saveProject(this.state.project)
.then(project => {
this.props.history.push(`/projects/${project.id}/edit`);
});
});
}
render() {
public render() {
return (
<div className="m-3 text-light">
<h3><i className="fas fa-sliders-h fa-1x"></i><span className="px-2">Project Settings</span></h3>
@ -87,4 +72,21 @@ export default class ProjectSettingsPage extends React.Component<ProjectSettings
</div>
);
}
private onFormSubmit = (form) => {
this.setState({
project: {
...form.formData,
sourceConnection: this.props.connections
.find((connection) => connection.id === form.formData.sourceConnectionId),
targetConnection: this.props.connections
.find((connection) => connection.id === form.formData.targetConnectionId),
},
}, () => {
this.props.projectActions.saveProject(this.state.project)
.then((project) => {
this.props.history.push(`/projects/${project.id}/edit`);
});
});
}
}

Просмотреть файл

@ -1,13 +1,13 @@
import React from 'react';
import { Route } from 'react-router-dom';
import HomePage from '../pages/homepage/homePage';
import ActiveLearningPage from '../pages/activeLearningPage';
import AppSettingsPage from '../pages/appSettings/appSettingsPage';
import ConnectionPage from '../pages/connections/connectionsPage';
import EditorPage from '../pages/editorPage';
import ExportPage from '../pages/exportPage';
import ProjectSettingsPage from '../pages/projectSettings/projectSettingsPage';
import ProfileSettingsPage from '../pages/profileSettingsPage';
import React from "react";
import { Route } from "react-router-dom";
import HomePage from "../pages/homepage/homePage";
import ActiveLearningPage from "../pages/activeLearningPage";
import AppSettingsPage from "../pages/appSettings/appSettingsPage";
import ConnectionPage from "../pages/connections/connectionsPage";
import EditorPage from "../pages/editorPage";
import ExportPage from "../pages/exportPage";
import ProjectSettingsPage from "../pages/projectSettings/projectSettingsPage";
import ProfileSettingsPage from "../pages/profileSettingsPage";
export default function MainContentRouter() {
return (
@ -24,4 +24,4 @@ export default function MainContentRouter() {
<Route path="/projects/:projectId/active-learning" component={ActiveLearningPage} />
</div>
);
}
}

Просмотреть файл

@ -1,25 +1,25 @@
import React from 'react';
import Navbar from './navbar';
import { BrowserRouter as Router } from 'react-router-dom';
import { shallow, mount } from 'enzyme';
import React from "react";
import Navbar from "./navbar";
import { BrowserRouter as Router } from "react-router-dom";
import { mount } from "enzyme";
describe('Sidebar Component', () => {
it('renders correctly', () => {
describe("Sidebar Component", () => {
it("renders correctly", () => {
const wrapper = mount(
<Router>
<Navbar />
</Router>
</Router>,
);
expect(wrapper).not.toBeNull();
const logo = wrapper.find('.app-navbar-logo ul li a');
const logo = wrapper.find(".app-navbar-logo ul li a");
expect(logo.length).toEqual(1);
const brand = wrapper.find('.app-navbar-brand span');
expect(brand.text()).toEqual('Visual Object Tagging Tool');
const brand = wrapper.find(".app-navbar-brand span");
expect(brand.text()).toEqual("Visual Object Tagging Tool");
const profile = wrapper.find('.app-navbar-menu ul li a')
const profile = wrapper.find(".app-navbar-menu ul li a");
expect(profile.length).toEqual(1);
});
});

Просмотреть файл

@ -1,5 +1,5 @@
import React from 'react';
import { Link, NavLink } from 'react-router-dom';
import React from "react";
import { Link, NavLink } from "react-router-dom";
export default function Navbar() {
return (
@ -14,9 +14,13 @@ export default function Navbar() {
</div>
<div className="app-navbar-menu">
<ul>
<li><NavLink title="Profile Settings" to={`/profile`}><i className="fas fa-user-circle"></i></NavLink></li>
<li>
<NavLink title="Profile Settings" to={`/profile`}>
<i className="fas fa-user-circle"></i>
</NavLink>
</li>
</ul>
</div>
</nav>
);
}
}

Просмотреть файл

@ -1,21 +1,21 @@
import React from 'react';
import Sidebar from './sidebar';
import { BrowserRouter as Router } from 'react-router-dom';
import { mount } from 'enzyme';
import { IProject } from '../../store/applicationState';
import React from "react";
import Sidebar from "./sidebar";
import { BrowserRouter as Router } from "react-router-dom";
import { mount } from "enzyme";
import { IProject } from "../../store/applicationState";
describe('Sidebar Component', () => {
it('renders correctly', () => {
describe("Sidebar Component", () => {
it("renders correctly", () => {
const project: IProject = null;
const wrapper = mount(
<Router>
<Sidebar project={project} />
</Router>
</Router>,
);
expect(wrapper).not.toBeNull();
const links = wrapper.find('ul li');
const links = wrapper.find("ul li");
expect(links.length).toEqual(6);
});
});

Просмотреть файл

@ -1,6 +1,6 @@
import React from 'react';
import { NavLink } from 'react-router-dom';
import ConditionalNavLink from '../common/conditionalNavLink';
import React from "react";
import { NavLink } from "react-router-dom";
import ConditionalNavLink from "../common/conditionalNavLink";
export default function Sidebar({ project }) {
const projectId = project ? project.id : null;
@ -8,11 +8,35 @@ export default function Sidebar({ project }) {
return (
<div className="bg-lighter-2 app-sidebar">
<ul>
<li><ConditionalNavLink disabled={!projectId} title="Tag Editor" to={`/projects/${projectId}/edit`}><i className="fas fa-bookmark"></i></ConditionalNavLink></li>
<li><ConditionalNavLink disabled={!projectId} title="Project Settings" to={`/projects/${projectId}/settings`}><i className="fas fa-sliders-h"></i></ConditionalNavLink></li>
<li><ConditionalNavLink disabled={!projectId} title="Export" to={`/projects/${projectId}/export`}><i className="fas fa-external-link-square-alt"></i></ConditionalNavLink></li>
<li><ConditionalNavLink disabled={!projectId} title="Active Learning" to={`/projects/${projectId}/active-learning`}><i className="fas fa-graduation-cap"></i></ConditionalNavLink></li>
<li><NavLink title="Connections" to={`/connections`}><i className="fas fa-plug"></i></NavLink></li>
<li>
<ConditionalNavLink disabled={!projectId}
title="Tag Editor"
to={`/projects/${projectId}/edit`}>
<i className="fas fa-bookmark"></i>
</ConditionalNavLink>
</li>
<li>
<ConditionalNavLink disabled={!projectId}
title="Project Settings"
to={`/projects/${projectId}/settings`}>
<i className="fas fa-sliders-h"></i>
</ConditionalNavLink>
</li>
<li>
<ConditionalNavLink disabled={!projectId}
title="Export"
to={`/projects/${projectId}/export`}>
<i className="fas fa-external-link-square-alt"></i></ConditionalNavLink></li>
<li>
<ConditionalNavLink disabled={!projectId}
title="Active Learning"
to={`/projects/${projectId}/active-learning`}>
<i className="fas fa-graduation-cap"></i>
</ConditionalNavLink>
</li>
<li>
<NavLink title="Connections" to={`/connections`}><i className="fas fa-plug"></i></NavLink>
</li>
</ul>
<div className="app-sidebar-fill"></div>
<ul>
@ -20,4 +44,4 @@ export default function Sidebar({ project }) {
</ul>
</div>
);
}
}

Просмотреть файл

@ -1,17 +1,16 @@
import React from 'react';
import { Provider } from 'react-redux';
import ReactDOM from 'react-dom';
import '../node_modules/bootstrap/dist/css/bootstrap.min.css';
import '../node_modules/@fortawesome/fontawesome-free/css/all.css';
import './assets/css/bootstrap-theme-slate.css';
import './index.scss';
import App from './App';
import * as serviceWorker from './serviceWorker';
import createReduxStore from './store/store';
import initialState from './store/initialState';
import ApplicationState from './store/applicationState';
import registerProviders from './registerProviders';
import React from "react";
import { Provider } from "react-redux";
import ReactDOM from "react-dom";
import "../node_modules/bootstrap/dist/css/bootstrap.min.css";
import "../node_modules/@fortawesome/fontawesome-free/css/all.css";
import "./assets/css/bootstrap-theme-slate.css";
import "./index.scss";
import App from "./App";
import * as serviceWorker from "./serviceWorker";
import createReduxStore from "./store/store";
import initialState from "./store/initialState";
import ApplicationState from "./store/applicationState";
import registerProviders from "./registerProviders";
registerProviders();
const defaultState: ApplicationState = initialState;
@ -21,7 +20,7 @@ ReactDOM.render(
<Provider store={store}>
<App />
</Provider>
, document.getElementById('root'));
, document.getElementById("root"));
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.

Просмотреть файл

@ -8,7 +8,8 @@
"object-literal-sort-keys": false,
"no-console": false,
"no-shadowed-variable": false,
"ordered-imports": false
"ordered-imports": false,
"no-string-literal": false
},
"rulesDirectory": []
}