Merge pull request #152 from status-im/feature/editor-tabs

Add editor tabs
This commit is contained in:
Iuri Matias 2018-10-24 14:12:32 -04:00 committed by GitHub
commit 1862848105
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 170 additions and 78 deletions

View File

@ -282,7 +282,7 @@ export const files = {
export const FILE = createRequestTypes('FILE');
export const file = {
request: (file) => action(FILE[REQUEST], file),
success: (file) => action(FILE[SUCCESS], file),
success: (file) => action(FILE[SUCCESS], {file}),
failure: (error) => action(FILE[FAILURE], {error})
};
@ -300,20 +300,6 @@ export const removeFile = {
failure: (error) => action(REMOVE_FILE[FAILURE], {error})
};
export const CURRENT_FILE = createRequestTypes('CURRENT_FILE');
export const currentFile = {
request: () => action(CURRENT_FILE[REQUEST]),
success: (file) => action(CURRENT_FILE[SUCCESS], {currentFiles: [file]}),
failure: () => action(CURRENT_FILE[FAILURE])
};
export const SAVE_CURRENT_FILE = createRequestTypes('SAVE_CURRENT_FILE');
export const saveCurrentFile = {
request: (file) => action(SAVE_CURRENT_FILE[REQUEST], file),
success: (file) => action(SAVE_CURRENT_FILE[SUCCESS], {currentFiles: [file]}),
failure: () => action(SAVE_CURRENT_FILE[FAILURE])
};
export const GAS_ORACLE = createRequestTypes('GAS_ORACLE');
export const gasOracle = {
request: () => action(GAS_ORACLE[REQUEST]),
@ -410,6 +396,27 @@ export const debuggerInfo = {
success: (data) => action(DEBUGGER_INFO[SUCCESS], {data})
};
export const FETCH_EDITOR_TABS = createRequestTypes('FETCH_EDITOR_TABS');
export const fetchEditorTabs = {
request: () => action(FETCH_EDITOR_TABS[REQUEST]),
success: (editorTabs) => action(FETCH_EDITOR_TABS[SUCCESS], {editorTabs}),
failure: () => action(FETCH_EDITOR_TABS[FAILURE])
};
export const ADD_EDITOR_TABS = createRequestTypes('ADD_EDITOR_TABS');
export const addEditorTabs = {
request: (file) => action(ADD_EDITOR_TABS[REQUEST], {file}),
success: () => action(ADD_EDITOR_TABS[SUCCESS]),
failure: () => action(ADD_EDITOR_TABS[FAILURE])
};
export const REMOVE_EDITOR_TABS = createRequestTypes('REMOVE_EDITOR_TABS');
export const removeEditorTabs = {
request: (file) => action(REMOVE_EDITOR_TABS[REQUEST], {file}),
success: () => action(REMOVE_EDITOR_TABS[SUCCESS]),
failure: () => action(REMOVE_EDITOR_TABS[FAILURE])
};
// Web Socket
export const WATCH_NEW_PROCESS_LOGS = 'WATCH_NEW_PROCESS_LOGS';
export const STOP_NEW_PROCESS_LOGS = 'STOP_NEW_PROCESS_LOGS';

View File

@ -1,12 +1,10 @@
import PropTypes from "prop-types";
import React from 'react';
import ReactJson from "react-json-view";
import {Row, Col, Table} from "reactstrap";
import {formatContractForDisplay} from '../utils/presentation';
import {Row, Col} from "reactstrap";
import CopyButton from './CopyButton';
const ContractDetail = ({contract}) => {
const contractDisplay = formatContractForDisplay(contract);
return (
<Row>
<Col>

View File

@ -1,6 +1,8 @@
import React from 'react';
import * as monaco from 'monaco-editor';
import PropTypes from 'prop-types';
import FontAwesomeIcon from 'react-fontawesome';
import classNames from 'classnames';
import './TextEditor.css';
@ -40,7 +42,7 @@ class TextEditor extends React.Component {
editor.onMouseDown((e) => {
if (e.target.type === GUTTER_GLYPH_MARGIN){
this.props.toggleBreakpoint(this.props.file.name, e.target.position.lineNumber);
this.props.toggleBreakpoint(this.props.currentFile.name, e.target.position.lineNumber);
}
});
}
@ -49,7 +51,10 @@ class TextEditor extends React.Component {
getLanguage() {
const extension = this.props.file.name.split('.').pop();
if (!this.props.currentFile.name) {
return DEFAULT_LANGUAGE;
}
const extension = this.props.currentFile.name.split('.').pop();
return SUPPORTED_LANGUAGES[SUPPORTED_LANGUAGES.indexOf(extension)] || DEFAULT_LANGUAGE;
}
@ -112,8 +117,8 @@ class TextEditor extends React.Component {
}
componentDidUpdate(prevProps) {
if (this.props.file.content !== prevProps.file.content) {
editor.setValue(this.props.file.content);
if (this.props.currentFile.content && this.props.currentFile.content !== prevProps.currentFile.content) {
editor.setValue(this.props.currentFile.content);
}
this.updateMarkers();
@ -125,9 +130,23 @@ class TextEditor extends React.Component {
this.handleResize();
}
renderTabs() {
return (
<ul className="list-inline m-0 p-2">
{this.props.editorTabs.map(file => (
<li key={file.name} className={classNames("list-inline-item", "border-right", "p-2", { 'bg-dark': file.name === this.props.currentFile.name })}>
<a className="text-white no-underline" href='#switch-tab' onClick={() => this.props.addEditorTabs(file)}>{file.name}</a>
<FontAwesomeIcon onClick={() => this.props.removeEditorTabs(file)} className="mx-1" name="close" />
</li>
))}
</ul>
)
}
render() {
return (
<div className="h-100 d-flex flex-column">
{this.renderTabs()}
<div style={{height: '100%'}} id={EDITOR_ID} />
</div>
)
@ -136,10 +155,13 @@ class TextEditor extends React.Component {
TextEditor.propTypes = {
onFileContentChange: PropTypes.func,
file: PropTypes.object,
currentFile: PropTypes.object,
toggleBreakpoint: PropTypes.func,
breakpoints: PropTypes.array,
debuggerLine: PropTypes.number
debuggerLine: PropTypes.number,
editorTabs: PropTypes.array,
removeEditorTabs: PropTypes.func,
addEditorTabs: PropTypes.func
};
export default TextEditor;

View File

@ -13,8 +13,6 @@ const TextEditorToolbar = (props) => (
</Label>
</Col>
<Col sm={4} md={6}>
<strong>{props.currentFile.name}</strong>
<span className="mx-2">|</span>
<Button color="success" size="sm" onClick={props.save}>
<FontAwesomeIcon className="mr-2" name="save"/>
Save
@ -55,7 +53,6 @@ const TextEditorToolbar = (props) => (
);
TextEditorToolbar.propTypes = {
currentFile: PropTypes.object,
isContract: PropTypes.bool,
save: PropTypes.func,
remove: PropTypes.func,

View File

@ -1,4 +1,5 @@
.editor--grid {
margin-left: -30px !important;
margin-right: -30px !important;
background-color: #1C1C1C;
}

View File

@ -6,13 +6,11 @@ import TextEditorAsideContainer from './TextEditorAsideContainer';
import TextEditorContainer from './TextEditorContainer';
import FileExplorerContainer from './FileExplorerContainer';
import TextEditorToolbarContainer from './TextEditorToolbarContainer';
import {currentFile as currentFileAction} from '../actions';
import {fetchEditorTabs as fetchEditorTabsAction} from '../actions';
import {getCurrentFile} from '../reducers/selectors';
import './EditorContainer.css';
const DEFAULT_FILE = {name: 'newContract.sol', content: ''};
class EditorContainer extends React.Component {
constructor(props) {
super(props)
@ -20,9 +18,7 @@ class EditorContainer extends React.Component {
}
componentDidMount() {
if(this.props.currentFile.content === '') {
this.props.fetchCurrentFile();
}
this.props.fetchEditorTabs();
}
componentDidUpdate(prevProps) {
@ -32,7 +28,7 @@ class EditorContainer extends React.Component {
}
isContract() {
return this.state.currentFile.name.endsWith('.sol');
return this.state.currentFile.name && this.state.currentFile.name.endsWith('.sol');
}
onFileContentChange(newContent) {
@ -84,7 +80,7 @@ class EditorContainer extends React.Component {
}
function mapStateToProps(state, props) {
const currentFile = getCurrentFile(state) || DEFAULT_FILE;
const currentFile = getCurrentFile(state);
return {
currentFile
@ -93,11 +89,11 @@ function mapStateToProps(state, props) {
EditorContainer.propTypes = {
currentFile: PropTypes.object,
fetchCurrentFile: PropTypes.func
fetchEditorTabs: PropTypes.func
};
export default connect(
mapStateToProps,
{fetchCurrentFile: currentFileAction.request},
{fetchEditorTabs: fetchEditorTabsAction.request},
)(EditorContainer);

View File

@ -3,23 +3,39 @@ import {connect} from 'react-redux';
import PropTypes from 'prop-types';
import TextEditor from '../components/TextEditor';
import {
addEditorTabs as addEditorTabsAction,
fetchEditorTabs as fetchEditorTabsAction,
removeEditorTabs as removeEditorTabsAction,
toggleBreakpoint,
} from '../actions';
import {getCurrentFile, getContractCompile, getContractDeploys, getBreakpointsByFilename, getDebuggerLine} from '../reducers/selectors';
import {getCurrentFile, getContractCompile, getContractDeploys, getBreakpointsByFilename, getDebuggerLine, getEditorTabs} from '../reducers/selectors';
const TextEditorContainer = (props) => (
<TextEditor file={props.currentFile}
breakpoints={props.breakpoints}
toggleBreakpoint={props.toggleBreakpoint}
debuggerLine={props.debuggerLine}
onFileContentChange={props.onFileContentChange} />
)
class TextEditorContainer extends React.Component {
componentDidMount() {
this.props.fetchEditorTabs();
}
render() {
return (
<TextEditor file={this.props.currentFile}
currentFile={this.props.currentFile}
breakpoints={this.props.breakpoints}
toggleBreakpoint={this.props.toggleBreakpoint}
editorTabs={this.props.editorTabs}
removeEditorTabs={this.props.removeEditorTabs}
addEditorTabs={this.props.addEditorTabs}
debuggerLine={this.props.debuggerLine}
onFileContentChange={this.props.onFileContentChange} />
)
}
}
function mapStateToProps(state, props) {
const breakpoints = getBreakpointsByFilename(state, props.currentFile.name);
const editorTabs = getEditorTabs(state);
const debuggerLine = getDebuggerLine(state);
return {breakpoints, debuggerLine};
return {breakpoints, editorTabs, debuggerLine};
}
TextEditorContainer.propTypes = {
@ -27,10 +43,18 @@ TextEditorContainer.propTypes = {
onFileContentChange: PropTypes.func,
toggleBreakpoints: PropTypes.func,
breakpoints: PropTypes.array,
toggleBreakpoint: PropTypes.object
toggleBreakpoint: PropTypes.object,
fetchEditorTabs: PropTypes.func,
removeEditorTabs: PropTypes.func,
addEditorTabs: PropTypes.func
};
export default connect(
mapStateToProps,
{toggleBreakpoint},
{
toggleBreakpoint,
fetchEditorTabs: fetchEditorTabsAction.request,
removeEditorTabs: removeEditorTabsAction.request,
addEditorTabs: addEditorTabsAction.request
},
)(TextEditorContainer);

View File

@ -73,3 +73,7 @@
background-image: url(images/icons.png);
}
.no-underline {
text-decoration: none;
}

View File

@ -2,7 +2,7 @@ import {combineReducers} from 'redux';
import {REQUEST, SUCCESS, FAILURE, CONTRACT_COMPILE, FILES, LOGOUT, AUTHENTICATE,
FETCH_CREDENTIALS, UPDATE_BASE_ETHER, CHANGE_THEME, FETCH_THEME, EXPLORER_SEARCH, DEBUGGER_INFO,
SIGN_MESSAGE, VERIFY_MESSAGE, TOGGLE_BREAKPOINT,
UPDATE_DEPLOYMENT_PIPELINE, WEB3_CONNECT, WEB3_DEPLOY, WEB3_ESTIMAGE_GAS} from "../actions";
UPDATE_DEPLOYMENT_PIPELINE, WEB3_CONNECT, WEB3_DEPLOY, WEB3_ESTIMAGE_GAS, FETCH_EDITOR_TABS} from "../actions";
import {EMBARK_PROCESS_NAME, DARK_THEME, DEPLOYMENT_PIPELINES, DEFAULT_HOST} from '../constants';
const BN_FACTOR = 10000;
@ -31,7 +31,6 @@ const entitiesDefaultState = {
ensRecords: [],
files: [],
gasOracleStats: [],
currentFiles: []
};
const sorter = {
@ -80,7 +79,7 @@ const sorter = {
if (a.name < b.name) return -1;
if (a.name > b.name) return 1;
return 0;
}
},
};
const filtrer = {
@ -354,6 +353,13 @@ function debuggerInfo(state={}, action) {
return state;
}
function editorTabs(state = [], action) {
if (action.type === FETCH_EDITOR_TABS[SUCCESS] && action.editorTabs) {
return action.editorTabs;
}
return state;
}
const rootReducer = combineReducers({
entities,
loading,
@ -368,9 +374,9 @@ const rootReducer = combineReducers({
breakpoints,
deploymentPipeline,
web3,
searchResult,
debuggerInfo,
theme
theme,
editorTabs
});
export default rootReducer;

View File

@ -170,7 +170,7 @@ export function getFiles(state) {
}
export function getCurrentFile(state) {
return last(state.entities.currentFiles);
return state.editorTabs.find(file => file.active) || {};
}
export function getBaseEther(state) {
@ -234,3 +234,6 @@ export function getDebuggerLine(state) {
return state.debuggerInfo.sources.lineColumnPos[0].start.line + 1;
}
export function getEditorTabs(state) {
return state.editorTabs
}

View File

@ -79,9 +79,6 @@ export const debugStepIntoBackward = doRequest.bind(null, actions.debugStepIntoB
export const toggleBreakpoint = doRequest.bind(null, actions.toggleBreakpoint, api.toggleBreakpoint);
export const authenticate = doRequest.bind(null, actions.authenticate, api.authenticate);
export const fetchCurrentFile = doRequest.bind(null, actions.currentFile, storage.fetchCurrentFile);
export const postCurrentFile = doRequest.bind(null, actions.saveCurrentFile, storage.postCurrentFile);
export const deleteCurrentFile = doRequest.bind(null, null, storage.deleteCurrentFile);
export const fetchCredentials = doRequest.bind(null, actions.fetchCredentials, storage.fetchCredentials);
export const saveCredentials = doRequest.bind(null, actions.saveCredentials, storage.saveCredentials);
export const logout = doRequest.bind(null, actions.logout, storage.logout);
@ -89,6 +86,9 @@ export const changeTheme = doRequest.bind(null, actions.changeTheme, storage.cha
export const fetchTheme = doRequest.bind(null, actions.fetchTheme, storage.fetchTheme);
export const signMessage = doRequest.bind(null, actions.signMessage, api.signMessage);
export const verifyMessage = doRequest.bind(null, actions.verifyMessage, api.verifyMessage);
export const fetchEditorTabs = doRequest.bind(null, actions.fetchEditorTabs, storage.fetchEditorTabs);
export const addEditorTabs = doRequest.bind(null, actions.addEditorTabs, storage.addEditorTabs);
export const removeEditorTabs = doRequest.bind(null, actions.removeEditorTabs, storage.removeEditorTabs);
export const explorerSearch = searchExplorer.bind(null, actions.explorerSearch);
@ -208,25 +208,17 @@ export function *watchPostFile() {
yield takeEvery(actions.SAVE_FILE[actions.REQUEST], postFile);
}
export function *watchPostFileSuccess() {
yield takeEvery(actions.SAVE_FILE[actions.SUCCESS], postCurrentFile);
}
export function *watchDeleteFile() {
yield takeEvery(actions.REMOVE_FILE[actions.REQUEST], deleteFile);
}
export function *watchDeleteFileSuccess() {
yield takeEvery(actions.REMOVE_FILE[actions.SUCCESS], fetchFiles);
yield takeEvery(actions.REMOVE_FILE[actions.SUCCESS], deleteCurrentFile);
yield takeEvery(actions.REMOVE_FILE[actions.SUCCESS], removeEditorTabs);
}
export function *watchFetchFileSuccess() {
yield takeEvery(actions.FILE[actions.SUCCESS], postCurrentFile);
}
export function *watchFetchCurrentFile() {
yield takeEvery(actions.CURRENT_FILE[actions.REQUEST], fetchCurrentFile);
yield takeEvery(actions.FILE[actions.SUCCESS], addEditorTabs);
}
export function *watchFetchEthGas() {
@ -313,6 +305,26 @@ export function *watchUpdateDeploymentPipeline() {
yield takeEvery(actions.UPDATE_DEPLOYMENT_PIPELINE, web3Connect);
}
export function *watchFetchEditorTabs() {
yield takeEvery(actions.FETCH_EDITOR_TABS[actions.REQUEST], fetchEditorTabs);
}
export function *watchAddEditorTabs() {
yield takeEvery(actions.ADD_EDITOR_TABS[actions.REQUEST], addEditorTabs);
}
export function *watchRemoveEditorTabs() {
yield takeEvery(actions.REMOVE_EDITOR_TABS[actions.REQUEST], removeEditorTabs);
}
export function *watchAddEditorTabsSuccess() {
yield takeEvery(actions.ADD_EDITOR_TABS[actions.SUCCESS], fetchEditorTabs);
}
export function *watchRemoveEditorTabsSuccess() {
yield takeEvery(actions.REMOVE_EDITOR_TABS[actions.SUCCESS], fetchEditorTabs);
}
function createChannel(socket) {
return eventChannel(emit => {
socket.onmessage = ((message) => {
@ -494,8 +506,6 @@ export default function *root() {
fork(watchDeleteFile),
fork(watchDeleteFileSuccess),
fork(watchFetchFileSuccess),
fork(watchFetchCurrentFile),
fork(watchPostFileSuccess),
fork(watchFetchCredentials),
fork(watchFetchEthGas),
fork(watchStartDebug),
@ -518,6 +528,11 @@ export default function *root() {
fork(watchWeb3EstimateGas),
fork(watchWeb3Deploy),
fork(watchUpdateDeploymentPipeline),
fork(watchListenDebugger)
fork(watchListenDebugger),
fork(watchFetchEditorTabs),
fork(watchAddEditorTabs),
fork(watchRemoveEditorTabs),
fork(watchAddEditorTabsSuccess),
fork(watchRemoveEditorTabsSuccess),
]);
}

View File

@ -1,20 +1,34 @@
export function postCurrentFile(file) {
export function addEditorTabs({file}) {
return new Promise(resolve => {
localStorage.setItem('currentFile', JSON.stringify(file));
resolve({response: {data: file}});
const editorTabs = findOrCreateEditorTabs();
editorTabs.forEach(f => f.active = false);
const alreadyAddedFile = editorTabs.find(f => f.name === file.name)
if (alreadyAddedFile) {
alreadyAddedFile.active = true
} else {
file.active = true
editorTabs.push(file);
}
localStorage.setItem('editorTabs', JSON.stringify(editorTabs));
resolve({response: {data: editorTabs}});
});
}
export function fetchCurrentFile() {
export function fetchEditorTabs() {
return new Promise(resolve => {
resolve({response: {data: JSON.parse(localStorage.getItem('currentFile'))}});
resolve({response: {data: JSON.parse(localStorage.getItem('editorTabs'))}});
});
}
export function deleteCurrentFile() {
export function removeEditorTabs({file}) {
return new Promise(resolve => {
localStorage.removeItem('currentFile');
resolve({});
const editorTabs = findOrCreateEditorTabs();
const filtered = editorTabs.filter(value => value.name !== file.name);
if (file.active && filtered.length) {
filtered[0].active = true;
}
localStorage.setItem('editorTabs', JSON.stringify(filtered));
resolve({response: {data: filtered}});
});
}
@ -50,3 +64,8 @@ export function changeTheme({theme}) {
export function fetchTheme() {
return new Promise(resolve => resolve({response: {data: localStorage.getItem('theme')}}));
}
function findOrCreateEditorTabs() {
return JSON.parse(localStorage.getItem('editorTabs')) || [];
}