From 05676674918217bbc6e178f1a8e687c4e244fad1 Mon Sep 17 00:00:00 2001 From: Gregory Schier Date: Sat, 23 Apr 2016 20:10:54 -0700 Subject: [PATCH 01/13] Got a hacky workspace implementation running --- app/components/RequestBodyEditor.js | 1 + app/components/Sidebar.js | 11 +-- app/containers/App.js | 16 ++-- app/containers/WorkspaceDropdown.js | 67 ++++++++++------- app/css/components/dropdown.scss | 1 - app/database/index.js | 104 +++++++++++++++++++------- app/index.js | 72 +++--------------- app/lib/import.js | 21 +++--- app/lib/{request.js => network.js} | 17 +++-- app/redux/create.js | 11 +-- app/redux/initstore.js | 59 +++++++++++++++ app/redux/modules/requestGroups.js | 11 +-- app/redux/modules/requests.js | 60 ++++++++------- app/redux/modules/responses.js | 2 +- app/redux/modules/workspaces.js | 51 ++++++++++--- app/redux/reducer.js | 18 ++++- package.json | 1 + webpack/webpack.config.development.js | 1 - 18 files changed, 332 insertions(+), 192 deletions(-) rename app/lib/{request.js => network.js} (82%) create mode 100644 app/redux/initstore.js diff --git a/app/components/RequestBodyEditor.js b/app/components/RequestBodyEditor.js index 436175103d..c9c09e16b0 100644 --- a/app/components/RequestBodyEditor.js +++ b/app/components/RequestBodyEditor.js @@ -10,6 +10,7 @@ class RequestBodyEditor extends Component { !r.parent); + filteredRequests = filteredRequests.filter(r => r.parentId === workspace._id); return filteredRequests.map(request => this.renderRequestRow(request)); } // Grab all of the children for this request group - filteredRequests = filteredRequests.filter(r => r.parent === requestGroup._id); + filteredRequests = filteredRequests.filter(r => r.parentId === requestGroup._id); // Don't show folder if it was not in the filter if (activeFilter && !filteredRequests.length) { @@ -126,7 +126,7 @@ class Sidebar extends Component { } render () { - const {activeFilter, requestGroups} = this.props; + const {activeFilter, requestGroups, workspace} = this.props; return (
@@ -163,6 +163,7 @@ Sidebar.propTypes = { activeFilter: PropTypes.string, requests: PropTypes.array.isRequired, requestGroups: PropTypes.array.isRequired, + workspace: PropTypes.object.isRequired, activeRequest: PropTypes.object }; diff --git a/app/containers/App.js b/app/containers/App.js index ec14081ab8..929722f7b8 100644 --- a/app/containers/App.js +++ b/app/containers/App.js @@ -192,16 +192,17 @@ class App extends Component { } render () { - const {actions, requests, responses, requestGroups, tabs, modals} = this.props; - const activeRequest = requests.all.find(r => r._id === requests.active); + const {actions, requests, responses, requestGroups, tabs, modals, workspaces} = this.props; + const activeRequest = requests.active; const activeResponse = activeRequest ? responses[activeRequest._id] : undefined; return (
db.requestCreate({parent: requestGroup._id})} + addRequestToRequestGroup={requestGroup => db.requestCreate({parentId: requestGroup._id})} toggleRequestGroup={requestGroup => db.update(requestGroup, {collapsed: !requestGroup.collapsed})} activeRequest={activeRequest} activeFilter={requests.filter} @@ -234,7 +235,6 @@ class App extends Component { App.propTypes = { actions: PropTypes.shape({ requests: PropTypes.shape({ - activate: PropTypes.func.isRequired, update: PropTypes.func.isRequired, remove: PropTypes.func.isRequired, send: PropTypes.func.isRequired, @@ -257,7 +257,10 @@ App.propTypes = { }).isRequired, requests: PropTypes.shape({ all: PropTypes.array.isRequired, - active: PropTypes.string // "required" but can be null + active: PropTypes.object + }).isRequired, + workspaces: PropTypes.shape({ + active: PropTypes.object }).isRequired, responses: PropTypes.object.isRequired, tabs: PropTypes.object.isRequired, @@ -267,6 +270,7 @@ App.propTypes = { function mapStateToProps (state) { return { actions: state.actions, + workspaces: state.workspaces, requests: state.requests, requestGroups: state.requestGroups, responses: state.responses, diff --git a/app/containers/WorkspaceDropdown.js b/app/containers/WorkspaceDropdown.js index 53bfac4490..55b1c7300b 100644 --- a/app/containers/WorkspaceDropdown.js +++ b/app/containers/WorkspaceDropdown.js @@ -8,6 +8,7 @@ import {connect} from 'react-redux' import Dropdown from '../components/base/Dropdown' import DropdownDivider from '../components/base/DropdownDivider' import * as RequestGroupActions from '../redux/modules/requestGroups' +import * as WorkspaceActions from '../redux/modules/workspaces' import * as db from '../database' import importData from '../lib/import' @@ -23,14 +24,16 @@ class WorkspaceDropdown extends Component { electron.remote.dialog.showOpenDialog(options, paths => { paths.map(path => { fs.readFile(path, 'utf8', (err, data) => { - err || importData(data); + err || importData(this.props.workspaces.active, data); }) }) }); } render () { - const {actions, loading, ...other} = this.props; + const {actions, loading, workspaces, ...other} = this.props; + + const workspace = workspaces.active; return ( @@ -46,21 +49,21 @@ class WorkspaceDropdown extends Component {
    - - - + + +
  • -
  • -
  • -
  • @@ -70,30 +73,29 @@ class WorkspaceDropdown extends Component {
  • -
  • - - + + + {workspaces.all.map(w => { + return w._id === workspaces.active._id ? null : ( +
  • + +
  • + ) + })}
  • - -
  • -
  • - -
  • -
  • -
  • - +
  • @@ -105,13 +107,23 @@ class WorkspaceDropdown extends Component { WorkspaceDropdown.propTypes = { loading: PropTypes.bool.isRequired, + workspaces: PropTypes.shape({ + all: PropTypes.array.isRequired, + active: PropTypes.object.isRequired + }), actions: PropTypes.shape({ - showEnvironmentEditModal: PropTypes.func.isRequired + requestGroups: PropTypes.shape({ + showEnvironmentEditModal: PropTypes.func.isRequired + }), + workspaces: PropTypes.shape({ + activate: PropTypes.func.isRequired + }) }) }; function mapStateToProps (state) { return { + workspaces: state.workspaces, actions: state.actions, loading: state.global.loading }; @@ -119,7 +131,10 @@ function mapStateToProps (state) { function mapDispatchToProps (dispatch) { return { - actions: bindActionCreators(RequestGroupActions, dispatch) + actions: { + requestGroups: bindActionCreators(RequestGroupActions, dispatch), + workspaces: bindActionCreators(WorkspaceActions, dispatch) + } } } diff --git a/app/css/components/dropdown.scss b/app/css/components/dropdown.scss index 0efabba944..1cdc6c2eb5 100644 --- a/app/css/components/dropdown.scss +++ b/app/css/components/dropdown.scss @@ -19,7 +19,6 @@ margin-bottom: 3px; border-radius: $radius-md; background: $bg-super-light; - padding: $padding-xs; overflow: hidden; li { diff --git a/app/database/index.js b/app/database/index.js index 6cd770b81b..5203d709de 100644 --- a/app/database/index.js +++ b/app/database/index.js @@ -21,8 +21,19 @@ export let changes = db.changes({ console.log('error', err); }); -export function allDocs () { - return db.allDocs({include_docs: true}); +/** + * Initialize the database. This should be called once on app start. + * @returns {Promise} + */ +export function initDB () { + console.log('-- Initializing Database --'); + return Promise.all([ + db.createIndex({index: {fields: ['parentId']}}), + db.createIndex({index: {fields: ['type']}}), + db.createIndex({index: {fields: ['activated', 'type']}}) + ]).catch(err => { + console.error('Failed to PouchDB Indexes', err); + }); } export function get (id) { @@ -40,6 +51,7 @@ export function update (doc, patch = {}) { return db.put(updatedDoc).catch(e => { if (e.status === 409) { console.warn('Retrying document update for', updatedDoc); + get(doc._id).then(dbDoc => { update(dbDoc, patch); }); @@ -47,8 +59,20 @@ export function update (doc, patch = {}) { }); } +export function getChildren (doc) { + const parentId = doc._id; + return db.find({selector: {parentId}}); +} + +export function removeChildren (doc) { + return getChildren(doc).then(res => res.docs.map(remove)); +} + export function remove (doc) { - return update(doc, {_deleted: true}); + return Promise.all([ + update(doc, {_deleted: true}), + removeChildren(doc) + ]); } // ~~~~~~~~~~~~~~~~~~~ // @@ -56,7 +80,12 @@ export function remove (doc) { // ~~~~~~~~~~~~~~~~~~~ // function modelCreate (type, idPrefix, defaults, patch = {}) { + const baseDefaults = { + parentId: null + }; + const model = Object.assign( + baseDefaults, defaults, patch, @@ -71,11 +100,9 @@ function modelCreate (type, idPrefix, defaults, patch = {}) { ); update(model); - return model; } - // ~~~~~~~ // // REQUEST // // ~~~~~~~ // @@ -85,18 +112,22 @@ export function requestCreate (patch = {}) { url: '', name: 'New Request', method: methods.METHOD_GET, + activated: Date.now(), body: '', params: [], contentType: 'text/plain', headers: [], - authentication: {}, - parent: null + authentication: {} }, patch); } -export function requestCopy (originalRequest) { - const name = `${originalRequest.name} (Copy)`; - return requestCreate(Object.assign({}, originalRequest, {name})); +export function requestCopy (request) { + const name = `${request.name} (Copy)`; + return requestCreate(Object.assign({}, request, {name})); +} + +export function requestActivate (request) { + return update(request, {activated: Date.now()}); } @@ -108,19 +139,16 @@ export function requestGroupCreate (patch = {}) { return modelCreate('RequestGroup', 'grp', { collapsed: false, name: 'New Request Group', - environment: {}, - parent: null + environment: {} }, patch); } - // ~~~~~~~~ // // RESPONSE // // ~~~~~~~~ // export function responseCreate (patch = {}) { return modelCreate('Response', 'rsp', { - requestId: null, statusCode: 0, statusMessage: '', contentType: 'text/plain', @@ -131,20 +159,12 @@ export function responseCreate (patch = {}) { }, patch); } -db.createIndex({ - index: {fields: ['requestId']} -}).catch(err => { - console.error('Failed to create index', err); -}).then(() => { - console.log('-- Indexes Updated --'); -}); - export function responseGetForRequest (request) { return db.find({ selector: { - requestId: request._id + parentId: request._id }, - sort: [{requestId: 'desc'}], + sort: [{parentId: 'desc'}], limit: 1 }) } @@ -156,7 +176,41 @@ export function responseGetForRequest (request) { export function workspaceCreate (patch = {}) { return modelCreate('Workspace', 'wsp', { - name: 'New Request Group', + name: 'New Workspace', + activated: Date.now(), environments: [] }, patch); } + +export function workspaceActivate (workspace) { + return update(workspace, {activated: Date.now()}); +} + +export function workspaceAll () { + return db.find({ + selector: {type: 'Workspace'} + }) +} + +export function workspaceGetActive () { + return db.find({ + selector: { + activated: {$gte: 0}, // HACK: because can't use $exists here? + type: {$eq: 'Workspace'} + }, + sort: [{activated: 'desc'}], + limit: 1 + }) +} + + +// ~~~~~~~~ // +// SETTINGS // +// ~~~~~~~~ // + +export function settingsCreate (patch = {}) { + return modelCreate('Settings', 'set', { + editorLineWrapping: false, + editorLineNumbers: true + }, patch); +} diff --git a/app/index.js b/app/index.js index 4420815f96..4c097a008f 100644 --- a/app/index.js +++ b/app/index.js @@ -1,75 +1,27 @@ import React from 'react' import {render} from 'react-dom' import {Provider} from 'react-redux' -import {bindActionCreators} from 'redux' import createStore from './redux/create' import App from './containers/App' -import * as RequestGroupActions from './redux/modules/requestGroups' -import * as RequestActions from './redux/modules/requests' -import * as ResponseActions from './redux/modules/responses' -import * as db from './database' - // Global CSS import './css/index.scss' import './css/lib/chrome/platform_app.css' import './css/lib/fontawesome/css/font-awesome.css' +import {initStore} from './redux/initstore' +import {initDB} from './database' -const store = createStore(); +export const store = createStore(); -// Dispatch the initial load of data -console.log('-- Init Insomnia --'); +console.log('-- Loading App --'); -const actionFns = { - RequestGroup: bindActionCreators(RequestGroupActions, store.dispatch), - Request: bindActionCreators(RequestActions, store.dispatch), - Response: bindActionCreators(ResponseActions, store.dispatch) -}; - -function refreshDoc (doc) { - const fns = actionFns[doc.type]; - - if (fns) { - fns[doc._deleted ? 'remove' : 'update'](doc); - } else if (doc.hasOwnProperty('type')) { - console.warn('Unknown change', doc.type, doc); - } else { - // Probably a design doc update or something... - } -} - -function watchDB () { - console.log('-- Watching PouchDB --'); - - let buffer = []; - let timeout = null; - - // Debounce and buffer changes if they happen in quick succession - db.changes.on('change', (response) => { - const doc = response.doc; - - buffer.push(doc); - clearTimeout(timeout); - - timeout = setTimeout(() => { - buffer.map(refreshDoc); - buffer = []; - }, 50); +initDB() + .then(() => initStore(store.dispatch)) + .then(() => { + console.log('-- Rendering App --'); + render( + , + document.getElementById('root') + ); }); -} - -function restoreDB() { - db.allDocs().then(response => { - response.rows.map(row => refreshDoc(row.doc)); - }) -} - -watchDB(); -restoreDB(); - -render( - , - document.getElementById('root') -); - diff --git a/app/lib/import.js b/app/lib/import.js index 7d1f9ecffe..e11c770465 100644 --- a/app/lib/import.js +++ b/app/lib/import.js @@ -6,9 +6,11 @@ const FORMAT_MAP = { 'json': 'application/json' }; -function importRequestGroup (iRequestGroup, exportFormat) { +function importRequestGroup (iRequestGroup, parentId, exportFormat) { if (exportFormat === 1) { const requestGroup = db.requestGroupCreate({ + parentId, + collapsed: true, name: iRequestGroup.name, environment: (iRequestGroup.environments || {}).base || {} }); @@ -24,7 +26,7 @@ function importRequestGroup (iRequestGroup, exportFormat) { } } -function importRequest (iRequest, parent, exportFormat) { +function importRequest (iRequest, parentId, exportFormat) { if (exportFormat === 1) { let auth = {}; if (iRequest.authentication.username) { @@ -35,6 +37,8 @@ function importRequest (iRequest, parent, exportFormat) { } db.requestCreate({ + parentId, + activated: 0, // Don't activate imported requests name: iRequest.name, url: iRequest.url, method: iRequest.method, @@ -42,13 +46,12 @@ function importRequest (iRequest, parent, exportFormat) { headers: iRequest.headers || [], params: iRequest.params || [], contentType: FORMAT_MAP[iRequest.__insomnia.format] || 'text/plain', - authentication: auth, - parent: parent + authentication: auth }); } } -export default function (txt, callback) { +export default function (workspace, txt, callback) { let data; try { @@ -62,11 +65,11 @@ export default function (txt, callback) { return callback(new Error('Invalid Insomnia export')); } - data.items.filter(i => i._type === TYPE_REQUEST_GROUP).map( - rg => importRequestGroup(rg, data.__export_format) + data.items.reverse().filter(i => i._type === TYPE_REQUEST_GROUP).map( + rg => importRequestGroup(rg, workspace._id, data.__export_format) ); - data.items.filter(i => i._type === TYPE_REQUEST).map( - r => importRequest(r, data.__export_format) + data.items.reverse().filter(i => i._type === TYPE_REQUEST).map( + r => importRequest(r, workspace._id, data.__export_format) ); } diff --git a/app/lib/request.js b/app/lib/network.js similarity index 82% rename from app/lib/request.js rename to app/lib/network.js index ff46c9eeb9..11d11e22d2 100644 --- a/app/lib/request.js +++ b/app/lib/network.js @@ -2,11 +2,11 @@ import networkRequest from 'request' import render from './render' import * as db from '../database' -function makeRequest (unrenderedRequest, callback, context = {}) { +function actuallySend (unrenderedRequest, callback, context = {}) { // SNEAKY HACK: Render nested object by converting it to JSON then rendering const template = JSON.stringify(unrenderedRequest); const request = JSON.parse(render(template, context)); - + const config = { method: request.method, body: request.body, @@ -35,6 +35,7 @@ function makeRequest (unrenderedRequest, callback, context = {}) { } } + // TODO: this needs to account for existing URL params config.url += request.params.map((p, i) => { const name = encodeURIComponent(p.name); const value = encodeURIComponent(p.value); @@ -47,7 +48,7 @@ function makeRequest (unrenderedRequest, callback, context = {}) { console.error('Request Failed', err, response); } else { db.responseCreate({ - requestId: request._id, + parentId: request._id, statusCode: response.statusCode, statusMessage: response.statusMessage, contentType: response.headers['content-type'], @@ -65,12 +66,12 @@ function makeRequest (unrenderedRequest, callback, context = {}) { }); } -export default function (request, callback) { - if (request.parent) { - db.get(request.parent).then( - requestGroup => makeRequest(request, callback, requestGroup.environment) +export function send (request, callback) { + if (request.parentId) { + db.get(request.parentId).then( + requestGroup => actuallySend(request, callback, requestGroup.environment) ); } else { - makeRequest(request, callback) + actuallySend(request, callback) } } diff --git a/app/redux/create.js b/app/redux/create.js index f2c74bc146..264c6c81e2 100644 --- a/app/redux/create.js +++ b/app/redux/create.js @@ -3,27 +3,22 @@ import thunkMiddleware from 'redux-thunk' import createLogger from 'redux-logger' import rootReducer from './reducer' -const loggerMiddleware = createLogger({ - collapsed: true -}); - export default function configureStore (initialState) { const store = createStore( rootReducer, initialState, applyMiddleware( thunkMiddleware, - loggerMiddleware + createLogger({collapsed: true}) ) ); if (module.hot) { - // Enable Webpack hot module replacement for reducers module.hot.accept('./reducer', () => { - const nextReducer = require('./reducer').default; + const nextReducer = require('./reducer.js').default; store.replaceReducer(nextReducer); }) } - return store + return store; } diff --git a/app/redux/initstore.js b/app/redux/initstore.js new file mode 100644 index 0000000000..02f4dc1d18 --- /dev/null +++ b/app/redux/initstore.js @@ -0,0 +1,59 @@ +import {bindActionCreators} from 'redux' +import * as RequestGroupActions from './modules/requestGroups' +import * as RequestActions from './modules/requests' +import * as ResponseActions from './modules/responses' +import * as WorkspaceActions from './modules/workspaces' +import * as db from '../database' + +// HACK: Because PouchDB doesn't let us unsub a single listener +let haveSubbedToPouch = false; + +export function initStore (dispatch) { + const actionFunctions = { + RequestGroup: bindActionCreators(RequestGroupActions, dispatch), + Request: bindActionCreators(RequestActions, dispatch), + Response: bindActionCreators(ResponseActions, dispatch), + Workspace: bindActionCreators(WorkspaceActions, dispatch) + }; + + const docChanged = doc => { + if (!doc.hasOwnProperty('type')) { + return; + } + + const fns = actionFunctions[doc.type]; + fns && fns[doc._deleted ? 'remove' : 'update'](doc); + }; + + console.log('-- Restoring Store --'); + + let docsThatChanged = []; + return db.workspaceAll().then(res => { + res.docs.map(doc => docsThatChanged.push(doc)); + }).then(() => { + return db.workspaceGetActive() + }).then(res => { + const workspace = res.docs.length ? res.docs[0] : db.workspaceCreate(); + + const restoreChildren = (doc, maxDepth, depth = 0) => { + docsThatChanged.push(doc); + + return db.getChildren(doc).then(res => { + return Promise.all( + res.docs.map(doc => restoreChildren(doc, maxDepth, depth++)) + ); + }) + }; + + return restoreChildren(workspace, 5); + }).then(() => { + // We saved all the updates for the end so the UI doesn't jitter + docsThatChanged.map(docChanged); + }).then(() => { + if (!haveSubbedToPouch) { + db.changes.on('change', res => docChanged(res.doc)); + haveSubbedToPouch = true; + } + }); +} + diff --git a/app/redux/modules/requestGroups.js b/app/redux/modules/requestGroups.js index e78920778e..77122973c7 100644 --- a/app/redux/modules/requestGroups.js +++ b/app/redux/modules/requestGroups.js @@ -1,11 +1,12 @@ import {combineReducers} from 'redux' import {show} from './modals' -import {MODAL_ENVIRONMENT_EDITOR, MODAL_REQUEST_GROUP_RENAME} from '../../lib/constants'; +import {MODAL_ENVIRONMENT_EDITOR, MODAL_REQUEST_GROUP_RENAME} from '../../lib/constants' +import {removeByParent} from './requests' -export const REQUEST_GROUP_UPDATE = 'requestgroups/update'; -export const REQUEST_GROUP_DELETE = 'requestgroups/delete'; -export const REQUEST_GROUP_TOGGLE = 'requestgroups/toggle'; +const REQUEST_GROUP_UPDATE = 'request-groups/update'; +const REQUEST_GROUP_DELETE = 'request-groups/delete'; +const REQUEST_GROUP_TOGGLE = 'request-groups/toggle'; // ~~~~~~~~ // // REDUCERS // @@ -15,7 +16,7 @@ function allReducer (state = [], action) { switch (action.type) { case REQUEST_GROUP_UPDATE: - const i = state.findIndex(r => r._id === action.requestGroup._id); + const i = state.findIndex(rg => rg._id === action.requestGroup._id); if (i === -1) { return [action.requestGroup, ...state]; diff --git a/app/redux/modules/requests.js b/app/redux/modules/requests.js index a7b18f2a1e..d28d2ab4eb 100644 --- a/app/redux/modules/requests.js +++ b/app/redux/modules/requests.js @@ -1,14 +1,14 @@ import {combineReducers} from 'redux' -import makeRequest from '../../lib/request' +import * as network from '../../lib/network' import {loadStart, loadStop} from './global' import {show} from './modals' import {MODAL_REQUEST_RENAME} from '../../lib/constants' -export const REQUEST_UPDATE = 'requests/update'; -export const REQUEST_DELETE = 'requests/delete'; -export const REQUEST_ACTIVATE = 'requests/activate'; -export const REQUEST_CHANGE_FILTER = 'requests/filter'; +const REQUEST_UPDATE = 'requests/update'; +const REQUEST_DELETE = 'requests/delete'; +const REQUEST_DELETE_BY_PARENT = 'requests/delete-by-parent'; +const REQUEST_CHANGE_FILTER = 'requests/filter'; // ~~~~~~~~ // // REDUCERS // @@ -20,6 +20,9 @@ function allReducer (state = [], action) { case REQUEST_DELETE: return state.filter(r => r._id !== action.request._id); + case REQUEST_DELETE_BY_PARENT: + return state.filter(r => r.parentId !== action.parentId); + case REQUEST_UPDATE: const i = state.findIndex(r => r._id === action.request._id); @@ -34,21 +37,6 @@ function allReducer (state = [], action) { } } -function activeReducer (state = null, action) { - switch (action.type) { - - case REQUEST_ACTIVATE: - return action.request._id; - - case REQUEST_DELETE: - return state === action._id ? null : state; - - default: - return state; - - } -} - function filterReducer (state = '', action) { switch (action.type) { case REQUEST_CHANGE_FILTER: @@ -58,6 +46,28 @@ function filterReducer (state = '', action) { } } +function activeReducer (state = null, action) { + switch (action.type) { + case REQUEST_UPDATE: + if (state && state._id === action.request._id) { + // Update the currently active request + return action.request; + } else if (state) { + // Activate a new request + return action.request.activated > state.activated ? action.request : state; + } else if (action.request.activated > 0) { + // Activate request if there isn't one + return action.request; + } else { + return state; + } + case REQUEST_DELETE: + return state && state._id === action.request._id ? null : state; + default: + return state; + } +} + export default combineReducers({ all: allReducer, filter: filterReducer, @@ -73,12 +83,12 @@ export function remove (request) { return {type: REQUEST_DELETE, request}; } -export function update (request) { - return {type: REQUEST_UPDATE, request}; +export function removeByParent (parentId) { + return {type: REQUEST_DELETE_BY_PARENT, parentId}; } -export function activate (request) { - return {type: REQUEST_ACTIVATE, request}; +export function update (request) { + return {type: REQUEST_UPDATE, request}; } export function changeFilter (filter) { @@ -89,7 +99,7 @@ export function send (request) { return dispatch => { dispatch(loadStart()); - makeRequest(request, () => { + network.send(request, () => { dispatch(loadStop()); }); } diff --git a/app/redux/modules/responses.js b/app/redux/modules/responses.js index 4bfe8a2e2f..b3523071b0 100644 --- a/app/redux/modules/responses.js +++ b/app/redux/modules/responses.js @@ -11,7 +11,7 @@ export default function (state = initialState, action) { case RESPONSE_UPDATE: return Object.assign({}, state, { - [action.response.requestId]: action.response + [action.response.parentId]: action.response }); default: diff --git a/app/redux/modules/workspaces.js b/app/redux/modules/workspaces.js index 0574331c93..8e012f142c 100644 --- a/app/redux/modules/workspaces.js +++ b/app/redux/modules/workspaces.js @@ -1,7 +1,11 @@ import {combineReducers} from 'redux' +import * as db from '../../database' +import {initStore} from '../initstore' -export const WORKSPACE_UPDATE = 'workspaces/update'; -export const WORKSPACE_DELETE = 'workspaces/delete'; +const WORKSPACE_UPDATE = 'workspaces/update'; +const WORKSPACE_DELETE = 'workspaces/delete'; + +// HACK: This one is used in the root reducer to reload everything export const WORKSPACE_ACTIVATE = 'workspaces/activate'; // ~~~~~~~~ // @@ -10,6 +14,19 @@ export const WORKSPACE_ACTIVATE = 'workspaces/activate'; function allReducer (state = [], action) { switch (action.type) { + + case WORKSPACE_UPDATE: + const i = state.findIndex(w => w._id === action.workspace._id); + + if (i === -1) { + return [action.workspace, ...state]; + } else { + return [...state.slice(0, i), action.workspace, ...state.slice(i + 1)] + } + + case WORKSPACE_DELETE: + return state.filter(w => w._id !== action.workspace._id); + default: return state; } @@ -17,9 +34,21 @@ function allReducer (state = [], action) { function activeReducer (state = null, action) { switch (action.type) { + + case WORKSPACE_UPDATE: + if (state && state._id === action.workspace._id) { + return action.workspace; + } else if (state) { + return action.workspace.activated > state.activated ? action.workspace : state; + } else { + return action.workspace; + } + + case WORKSPACE_DELETE: + return state && state._id === action.workspace._id ? null : state; + default: return state; - } } @@ -41,15 +70,19 @@ export default combineReducers({ // ACTIONS // // ~~~~~~~ // -export function remove (request) { - return {type: WORKSPACE_DELETE, request}; +export function remove (workspace) { + return {type: WORKSPACE_DELETE, workspace}; } -export function update (request) { - return {type: WORKSPACE_UPDATE, request}; +export function update (workspace) { + return {type: WORKSPACE_UPDATE, workspace}; } -export function activate (request) { - return {type: WORKSPACE_ACTIVATE, request}; +export function activate (workspace) { + return dispatch => { + db.workspaceActivate(workspace); + initStore(dispatch); + dispatch({type: WORKSPACE_ACTIVATE, workspace}); + } } diff --git a/app/redux/reducer.js b/app/redux/reducer.js index 99e495648c..91ee8c3654 100644 --- a/app/redux/reducer.js +++ b/app/redux/reducer.js @@ -7,12 +7,24 @@ import globalReducer from './modules/global' import modalsReducer from './modules/modals' import requestGroupsReducer from './modules/requestGroups' import responsesReducer from './modules/responses' +import {WORKSPACE_ACTIVATE} from './modules/workspaces' + +function clearOnWorkspaceActivate (reducer) { + return function (state, action) { + if (action.type === WORKSPACE_ACTIVATE) { + console.log('-- CLEARING STATE --'); + state = undefined; + } + + return reducer(state, action); + } +} export default combineReducers({ workspaces: workspacesReducer, - requestGroups: requestGroupsReducer, - requests: requestsReducer, - responses: responsesReducer, + requestGroups: clearOnWorkspaceActivate(requestGroupsReducer), + requests: clearOnWorkspaceActivate(requestsReducer), + responses: clearOnWorkspaceActivate(responsesReducer), modals: modalsReducer, global: globalReducer, tabs: tabsReducer diff --git a/package.json b/package.json index fe69425e31..548246a964 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "react-tabs": "^0.5.3", "redux": "^3.3.1", "redux-logger": "^2.6.1", + "redux-shortcuts": "0.0.1", "redux-thunk": "^2.0.1" }, "devDependencies": { diff --git a/webpack/webpack.config.development.js b/webpack/webpack.config.development.js index 3f6c5ac502..d18a622200 100644 --- a/webpack/webpack.config.development.js +++ b/webpack/webpack.config.development.js @@ -1,4 +1,3 @@ -import path from 'path' import webpack from 'webpack' import baseConfig from './webpack.config.base' From 80b6ac2a8fa5d3b8ea639be9940b043790dbaa9a Mon Sep 17 00:00:00 2001 From: Gregory Schier Date: Sat, 23 Apr 2016 21:26:19 -0700 Subject: [PATCH 02/13] Removed some hax with reducer composition --- app/containers/App.js | 28 +++++++------- app/containers/Prompts.js | 4 +- app/containers/RequestActionsDropdown.js | 2 +- app/containers/RequestGroupActionsDropdown.js | 2 +- app/containers/WorkspaceDropdown.js | 2 +- app/database/index.js | 23 ++++------- app/redux/initstore.js | 6 +-- .../{workspaces.js => workspaces/index.js} | 38 ++++++++++++++++--- .../modules/{ => workspaces}/requestGroups.js | 4 +- .../modules/{ => workspaces}/requests.js | 8 ++-- .../modules/{ => workspaces}/responses.js | 0 app/redux/reducer.js | 18 --------- 12 files changed, 67 insertions(+), 68 deletions(-) rename app/redux/modules/{workspaces.js => workspaces/index.js} (61%) rename app/redux/modules/{ => workspaces}/requestGroups.js (96%) rename app/redux/modules/{ => workspaces}/requests.js (92%) rename app/redux/modules/{ => workspaces}/responses.js (100%) diff --git a/app/containers/App.js b/app/containers/App.js index 929722f7b8..4414add110 100644 --- a/app/containers/App.js +++ b/app/containers/App.js @@ -16,8 +16,8 @@ import Sidebar from '../components/Sidebar' import EnvironmentEditModal from '../components/EnvironmentEditModal' import * as GlobalActions from '../redux/modules/global' -import * as RequestGroupActions from '../redux/modules/requestGroups' -import * as RequestActions from '../redux/modules/requests' +import * as RequestGroupActions from '../redux/modules/workspaces/requestGroups' +import * as RequestActions from '../redux/modules/workspaces/requests' import * as ModalActions from '../redux/modules/modals' import * as TabActions from '../redux/modules/tabs' @@ -192,7 +192,8 @@ class App extends Component { } render () { - const {actions, requests, responses, requestGroups, tabs, modals, workspaces} = this.props; + const {actions, tabs, modals, workspaces} = this.props; + const {requests, requestGroups, responses} = workspaces; const activeRequest = requests.active; const activeResponse = activeRequest ? responses[activeRequest._id] : undefined; @@ -252,17 +253,17 @@ App.propTypes = { select: PropTypes.func.isRequired }) }).isRequired, - requestGroups: PropTypes.shape({ - all: PropTypes.array.isRequired - }).isRequired, - requests: PropTypes.shape({ - all: PropTypes.array.isRequired, - active: PropTypes.object - }).isRequired, workspaces: PropTypes.shape({ - active: PropTypes.object + active: PropTypes.object, + responses: PropTypes.object.isRequired, + requestGroups: PropTypes.shape({ + all: PropTypes.array.isRequired + }).isRequired, + requests: PropTypes.shape({ + all: PropTypes.array.isRequired, + active: PropTypes.object + }).isRequired }).isRequired, - responses: PropTypes.object.isRequired, tabs: PropTypes.object.isRequired, modals: PropTypes.array.isRequired }; @@ -271,9 +272,6 @@ function mapStateToProps (state) { return { actions: state.actions, workspaces: state.workspaces, - requests: state.requests, - requestGroups: state.requestGroups, - responses: state.responses, tabs: state.tabs, modals: state.modals }; diff --git a/app/containers/Prompts.js b/app/containers/Prompts.js index c5dabbd459..dcb2e3073f 100644 --- a/app/containers/Prompts.js +++ b/app/containers/Prompts.js @@ -3,8 +3,8 @@ import {connect} from 'react-redux' import {bindActionCreators} from 'redux' import * as ModalActions from '../redux/modules/modals' -import * as RequestGroupActions from '../redux/modules/requestGroups' -import * as RequestActions from '../redux/modules/requests' +import * as RequestGroupActions from '../redux/modules/workspaces/requestGroups' +import * as RequestActions from '../redux/modules/workspaces/requests' import PromptModal from '../components/base/PromptModal' import * as db from '../database' diff --git a/app/containers/RequestActionsDropdown.js b/app/containers/RequestActionsDropdown.js index 6b9a48b5d6..78ec404ced 100644 --- a/app/containers/RequestActionsDropdown.js +++ b/app/containers/RequestActionsDropdown.js @@ -2,7 +2,7 @@ import React, {Component, PropTypes} from 'react' import {bindActionCreators} from 'redux' import {connect} from 'react-redux' import Dropdown from '../components/base/Dropdown' -import * as RequestActions from '../redux/modules/requests' +import * as RequestActions from '../redux/modules/workspaces/requests' import * as db from '../database'; class RequestActionsDropdown extends Component { diff --git a/app/containers/RequestGroupActionsDropdown.js b/app/containers/RequestGroupActionsDropdown.js index 1ae002b371..c62f3fbbd9 100644 --- a/app/containers/RequestGroupActionsDropdown.js +++ b/app/containers/RequestGroupActionsDropdown.js @@ -2,7 +2,7 @@ import React, {Component, PropTypes} from 'react' import {bindActionCreators} from 'redux' import {connect} from 'react-redux' import Dropdown from '../components/base/Dropdown' -import * as RequestGroupActions from '../redux/modules/requestGroups' +import * as RequestGroupActions from '../redux/modules/workspaces/requestGroups' import * as db from '../database' class RequestGroupActionsDropdown extends Component { diff --git a/app/containers/WorkspaceDropdown.js b/app/containers/WorkspaceDropdown.js index 55b1c7300b..de272576bf 100644 --- a/app/containers/WorkspaceDropdown.js +++ b/app/containers/WorkspaceDropdown.js @@ -7,7 +7,7 @@ import {connect} from 'react-redux' import Dropdown from '../components/base/Dropdown' import DropdownDivider from '../components/base/DropdownDivider' -import * as RequestGroupActions from '../redux/modules/requestGroups' +import * as RequestGroupActions from '../redux/modules/workspaces/requestGroups' import * as WorkspaceActions from '../redux/modules/workspaces' import * as db from '../database' import importData from '../lib/import' diff --git a/app/database/index.js b/app/database/index.js index 5203d709de..118f3ce9de 100644 --- a/app/database/index.js +++ b/app/database/index.js @@ -159,16 +159,6 @@ export function responseCreate (patch = {}) { }, patch); } -export function responseGetForRequest (request) { - return db.find({ - selector: { - parentId: request._id - }, - sort: [{parentId: 'desc'}], - limit: 1 - }) -} - // ~~~~~~~~~ // // WORKSPACE // @@ -208,9 +198,10 @@ export function workspaceGetActive () { // SETTINGS // // ~~~~~~~~ // -export function settingsCreate (patch = {}) { - return modelCreate('Settings', 'set', { - editorLineWrapping: false, - editorLineNumbers: true - }, patch); -} +// TODO: This +// export function settingsCreate (patch = {}) { +// return modelCreate('Settings', 'set', { +// editorLineWrapping: false, +// editorLineNumbers: true +// }, patch); +// } diff --git a/app/redux/initstore.js b/app/redux/initstore.js index 02f4dc1d18..d29ead00f5 100644 --- a/app/redux/initstore.js +++ b/app/redux/initstore.js @@ -1,7 +1,7 @@ import {bindActionCreators} from 'redux' -import * as RequestGroupActions from './modules/requestGroups' -import * as RequestActions from './modules/requests' -import * as ResponseActions from './modules/responses' +import * as RequestGroupActions from './modules/workspaces/requestGroups' +import * as RequestActions from './modules/workspaces/requests' +import * as ResponseActions from './modules/workspaces/responses' import * as WorkspaceActions from './modules/workspaces' import * as db from '../database' diff --git a/app/redux/modules/workspaces.js b/app/redux/modules/workspaces/index.js similarity index 61% rename from app/redux/modules/workspaces.js rename to app/redux/modules/workspaces/index.js index 8e012f142c..057817b907 100644 --- a/app/redux/modules/workspaces.js +++ b/app/redux/modules/workspaces/index.js @@ -1,6 +1,10 @@ import {combineReducers} from 'redux' -import * as db from '../../database' -import {initStore} from '../initstore' +import * as db from '../../../database' +import {initStore} from '../../initstore' + +import responsesReducer from './responses' +import requestsReducer from './requests' +import requestGroupsReducer from './requestGroups' const WORKSPACE_UPDATE = 'workspaces/update'; const WORKSPACE_DELETE = 'workspaces/delete'; @@ -59,12 +63,37 @@ function filterReducer (state = '', action) { } } -export default combineReducers({ +const workspaceReducer = combineReducers({ all: allReducer, filter: filterReducer, - active: activeReducer + active: activeReducer, + + // Nested resources + responses: responsesReducer, + requests: requestsReducer, + requestGroups: requestGroupsReducer }); +export default function (state = {}, action) { + // Call the reducer manually, so we can check if the active workspace has changed + let newState = workspaceReducer(state, action); + + const oldActiveId = state.active && state.active._id; + const newActiveId = newState.active && newState.active._id; + const activeWorkspaceChanged = oldActiveId !== newActiveId; + + // Clear all of these things if the workspace has changed + if (activeWorkspaceChanged) { + newState = Object.assign({}, newState, { + responses: responsesReducer(undefined, action), + requests: requestsReducer(undefined, action), + requestGroups: requestGroupsReducer(undefined, action) + }) + } + + return newState; +} + // ~~~~~~~ // // ACTIONS // @@ -82,7 +111,6 @@ export function activate (workspace) { return dispatch => { db.workspaceActivate(workspace); initStore(dispatch); - dispatch({type: WORKSPACE_ACTIVATE, workspace}); } } diff --git a/app/redux/modules/requestGroups.js b/app/redux/modules/workspaces/requestGroups.js similarity index 96% rename from app/redux/modules/requestGroups.js rename to app/redux/modules/workspaces/requestGroups.js index 77122973c7..35d1aa6f48 100644 --- a/app/redux/modules/requestGroups.js +++ b/app/redux/modules/workspaces/requestGroups.js @@ -1,7 +1,7 @@ import {combineReducers} from 'redux' -import {show} from './modals' -import {MODAL_ENVIRONMENT_EDITOR, MODAL_REQUEST_GROUP_RENAME} from '../../lib/constants' +import {show} from './../modals' +import {MODAL_ENVIRONMENT_EDITOR, MODAL_REQUEST_GROUP_RENAME} from '../../../lib/constants' import {removeByParent} from './requests' const REQUEST_GROUP_UPDATE = 'request-groups/update'; diff --git a/app/redux/modules/requests.js b/app/redux/modules/workspaces/requests.js similarity index 92% rename from app/redux/modules/requests.js rename to app/redux/modules/workspaces/requests.js index d28d2ab4eb..64832ce321 100644 --- a/app/redux/modules/requests.js +++ b/app/redux/modules/workspaces/requests.js @@ -1,9 +1,9 @@ import {combineReducers} from 'redux' -import * as network from '../../lib/network' -import {loadStart, loadStop} from './global' -import {show} from './modals' -import {MODAL_REQUEST_RENAME} from '../../lib/constants' +import * as network from '../../../lib/network' +import {loadStart, loadStop} from './../global' +import {show} from './../modals' +import {MODAL_REQUEST_RENAME} from '../../../lib/constants' const REQUEST_UPDATE = 'requests/update'; const REQUEST_DELETE = 'requests/delete'; diff --git a/app/redux/modules/responses.js b/app/redux/modules/workspaces/responses.js similarity index 100% rename from app/redux/modules/responses.js rename to app/redux/modules/workspaces/responses.js diff --git a/app/redux/reducer.js b/app/redux/reducer.js index 91ee8c3654..1015d11c3b 100644 --- a/app/redux/reducer.js +++ b/app/redux/reducer.js @@ -1,30 +1,12 @@ import {combineReducers} from 'redux' import workspacesReducer from './modules/workspaces' -import requestsReducer from './modules/requests' import tabsReducer from './modules/tabs' import globalReducer from './modules/global' import modalsReducer from './modules/modals' -import requestGroupsReducer from './modules/requestGroups' -import responsesReducer from './modules/responses' -import {WORKSPACE_ACTIVATE} from './modules/workspaces' - -function clearOnWorkspaceActivate (reducer) { - return function (state, action) { - if (action.type === WORKSPACE_ACTIVATE) { - console.log('-- CLEARING STATE --'); - state = undefined; - } - - return reducer(state, action); - } -} export default combineReducers({ workspaces: workspacesReducer, - requestGroups: clearOnWorkspaceActivate(requestGroupsReducer), - requests: clearOnWorkspaceActivate(requestsReducer), - responses: clearOnWorkspaceActivate(responsesReducer), modals: modalsReducer, global: globalReducer, tabs: tabsReducer From d9ea4d0b8b9e723b05f131ab577e1d746b9d3969 Mon Sep 17 00:00:00 2001 From: Gregory Schier Date: Sat, 23 Apr 2016 22:50:28 -0700 Subject: [PATCH 03/13] Moved some more around --- app/components/Sidebar.js | 4 +- app/containers/App.js | 52 +++++++++++-------- app/css/components/tabs.scss | 6 +-- app/css/layout/base.scss | 7 ++- app/database/index.js | 16 +++++- app/lib/import.js | 17 +++--- app/redux/initstore.js | 50 +++++++++++------- app/redux/modules/workspaces/index.js | 30 ++++++++--- app/redux/modules/workspaces/requestGroups.js | 8 ++- app/redux/modules/workspaces/requests.js | 31 ++++++++--- app/redux/modules/workspaces/responses.js | 12 +++++ 11 files changed, 166 insertions(+), 67 deletions(-) diff --git a/app/components/Sidebar.js b/app/components/Sidebar.js index a4bb827cce..e492fc1244 100644 --- a/app/components/Sidebar.js +++ b/app/components/Sidebar.js @@ -126,8 +126,8 @@ class Sidebar extends Component { } render () { - const {activeFilter, requestGroups, workspace} = this.props; - + const {activeFilter, requestGroups} = this.props; + return (
    diff --git a/app/containers/App.js b/app/containers/App.js index 4414add110..6afa7895d6 100644 --- a/app/containers/App.js +++ b/app/containers/App.js @@ -6,6 +6,7 @@ import {Tab, Tabs, TabList, TabPanel} from 'react-tabs' import Editor from '../components/base/Editor' import Prompts from './Prompts' import KeyValueEditor from '../components/base/KeyValueEditor' +import Dropdown from '../components/base/Dropdown' import RequestBodyEditor from '../components/RequestBodyEditor' import RequestAuthEditor from '../components/RequestAuthEditor' import RequestUrlBar from '../components/RequestUrlBar' @@ -60,13 +61,23 @@ class App extends Component { onSelect={i => actions.tabs.select('request', i)} selectedIndex={tabs.request || 0}> - + + + + +
      +
    • +
    • +
    • +
    • +
    +
    +
    - @@ -114,9 +114,6 @@ WorkspaceDropdown.propTypes = { actions: PropTypes.shape({ requestGroups: PropTypes.shape({ showEnvironmentEditModal: PropTypes.func.isRequired - }), - workspaces: PropTypes.shape({ - activate: PropTypes.func.isRequired }) }) }; @@ -132,8 +129,7 @@ function mapStateToProps (state) { function mapDispatchToProps (dispatch) { return { actions: { - requestGroups: bindActionCreators(RequestGroupActions, dispatch), - workspaces: bindActionCreators(WorkspaceActions, dispatch) + requestGroups: bindActionCreators(RequestGroupActions, dispatch) } } } diff --git a/app/database/index.js b/app/database/index.js index 4f80879ab4..320c4686c5 100644 --- a/app/database/index.js +++ b/app/database/index.js @@ -2,6 +2,11 @@ import * as methods from '../lib/constants'; import {generateId} from './util' +export const TYPE_WORKSPACE = 'Workspace'; +export const TYPE_REQUEST_GROUP = 'RequestGroup'; +export const TYPE_REQUEST = 'Request'; +export const TYPE_RESPONSE = 'Response'; + // We have to include the web version of PouchDB in app.html because // the NodeJS version defaults to LevelDB which is hard (impossible?) // to get working in Electron apps @@ -122,7 +127,7 @@ function modelCreate (type, idPrefix, defaults, patch = {}) { // ~~~~~~~ // export function requestCreate (patch = {}) { - return modelCreate('Request', 'req', { + return modelCreate(TYPE_REQUEST, 'req', { url: '', name: 'New Request', method: methods.METHOD_GET, @@ -150,7 +155,7 @@ export function requestActivate (request) { // ~~~~~~~~~~~~~ // export function requestGroupCreate (patch = {}) { - return modelCreate('RequestGroup', 'grp', { + return modelCreate(TYPE_REQUEST_GROUP, 'grp', { collapsed: false, name: 'New Request Group', environment: {} @@ -162,7 +167,7 @@ export function requestGroupCreate (patch = {}) { // ~~~~~~~~ // export function responseCreate (patch = {}) { - return modelCreate('Response', 'rsp', { + return modelCreate(TYPE_RESPONSE, 'res', { statusCode: 0, statusMessage: '', contentType: 'text/plain', @@ -179,9 +184,10 @@ export function responseCreate (patch = {}) { // ~~~~~~~~~ // export function workspaceCreate (patch = {}) { - return modelCreate('Workspace', 'wsp', { + return modelCreate(TYPE_WORKSPACE, 'wrk', { name: 'New Workspace', - activated: Date.now(), + activeRequestId: null, + activated: Date.now(), // TODO: Delete this property (replace with something better) environments: [] }, patch); } diff --git a/app/redux/initstore.js b/app/redux/initstore.js index c36b92b907..0555c23130 100644 --- a/app/redux/initstore.js +++ b/app/redux/initstore.js @@ -1,50 +1,36 @@ import {bindActionCreators} from 'redux' -import * as RequestGroupActions from './modules/requestGroups' -import * as RequestActions from './modules/requests' -import * as ResponseActions from './modules/responses' -import * as WorkspaceActions from './modules/workspaces' +import * as entitiesActions from './modules/entities' import * as db from '../database' const CHANGE_ID = 'store.listener'; export function initStore (dispatch) { db.offChange(CHANGE_ID); - - const actionFunctions = [ - // NOTE: Order matters here. Should be top -> bottom tree - ['Workspace', bindActionCreators(WorkspaceActions, dispatch)], - ['RequestGroup', bindActionCreators(RequestGroupActions, dispatch)], - ['Request', bindActionCreators(RequestActions, dispatch)], - ['Response', bindActionCreators(ResponseActions, dispatch)] - ]; + + // New stuff... + const entities = bindActionCreators(entitiesActions, dispatch); const docChanged = doc => { if (!doc.hasOwnProperty('type')) { return; } - - const [_, fns] = actionFunctions.find(fn => fn[0] === doc.type); - fns && fns[doc._deleted ? 'remove' : 'update'](doc); + + // New stuff... + entities[doc._deleted ? 'remove' : 'update'](doc); }; console.log('-- Restoring Store --'); - // Start building a list of docs by type. Keyed by _id so it doesn't - // matter if we try adding the same one twice - let docsThatChanged = {}; - const start = Date.now(); return db.workspaceAll().then(res => { - res.docs.map(doc => { - docsThatChanged[doc._id] = doc; - }); + res.docs.map(docChanged); }).then(() => { return db.workspaceGetActive() }).then(res => { const workspace = res.docs.length ? res.docs[0] : db.workspaceCreate(); const restoreChildren = (doc, maxDepth, depth = 0) => { - docsThatChanged[doc._id] = doc; + docChanged(doc); return db.getChildren(doc).then(res => { return Promise.all( @@ -55,16 +41,6 @@ export function initStore (dispatch) { return restoreChildren(workspace, 5); }).then(() => { - actionFunctions.map(tuple => { - const [t, fns] = tuple; - const docs = Object.keys(docsThatChanged).map( - t => docsThatChanged[t] - ).filter( - doc => doc.type === t - ); - - fns.replace(docs) - }); console.log(`Restore took ${(Date.now() - start) / 1000} s`); }).then(() => { db.onChange(CHANGE_ID, res => docChanged(res.doc)); diff --git a/app/redux/modules/entities.js b/app/redux/modules/entities.js new file mode 100644 index 0000000000..83276b7efa --- /dev/null +++ b/app/redux/modules/entities.js @@ -0,0 +1,101 @@ +import {combineReducers} from 'redux' + +import {TYPE_WORKSPACE, TYPE_REQUEST_GROUP, TYPE_REQUEST, TYPE_RESPONSE} from '../../database/index' +import * as workspaceFns from './workspaces' +import * as requestGroupFns from './requestGroups' +import * as requestFns from './requests' +import * as responseFns from './responses' + + +// ~~~~~~~~ // +// REDUCERS // +// ~~~~~~~~ // + +function generateEntityReducer (referenceName, updateAction, deleteAction) { + return function (state = {}, action) { + switch (action.type) { + + case updateAction: + const doc = action[referenceName]; + return {...state, [doc._id]: doc}; + + case deleteAction: + const newState = Object.assign({}, state); + delete newState[action[referenceName]._id]; + return newState; + + default: + return state; + } + } +} + +const workspaces = generateEntityReducer( + 'workspace', + workspaceFns.WORKSPACE_UPDATE, + workspaceFns.WORKSPACE_DELETE +); + +const requestGroups = generateEntityReducer( + 'requestGroup', + requestGroupFns.REQUEST_GROUP_UPDATE, + requestGroupFns.REQUEST_GROUP_DELETE +); + +const requests = generateEntityReducer( + 'request', + requestFns.REQUEST_UPDATE, + requestFns.REQUEST_DELETE +); + +const responses = generateEntityReducer( + 'response', + responseFns.RESPONSE_UPDATE, + responseFns.RESPONSE_DELETE +); + +export default combineReducers({ + workspaces, + requestGroups, + requests, + responses +}) + + +// ~~~~~~~ // +// ACTIONS // +// ~~~~~~~ // + +const updateFns = { + [TYPE_WORKSPACE]: workspaceFns.update, + [TYPE_REQUEST_GROUP]: requestGroupFns.update, + [TYPE_REQUEST]: requestFns.update, + [TYPE_RESPONSE]: responseFns.update +}; + +const removeFns = { + [TYPE_WORKSPACE]: workspaceFns.remove, + [TYPE_REQUEST_GROUP]: requestGroupFns.remove, + [TYPE_REQUEST]: requestFns.remove + // Response doesn't have a remove function (yet...) +}; + +export function update (doc) { + // Using dispatch here because Redux gets mad if we don't return anything + // in a normal action creator + return dispatch => { + if (updateFns.hasOwnProperty(doc.type)) { + dispatch(updateFns[doc.type](doc)); + } + } +} + +export function remove (doc) { + // Using dispatch here because Redux gets mad if we don't return anything + // in a normal action creator + return dispatch => { + if (removeFns.hasOwnProperty(doc.type)) { + dispatch(removeFns[doc.type](doc)); + } + } +} diff --git a/app/redux/modules/requestGroups.js b/app/redux/modules/requestGroups.js index 937739f498..aae168e926 100644 --- a/app/redux/modules/requestGroups.js +++ b/app/redux/modules/requestGroups.js @@ -3,10 +3,9 @@ import {combineReducers} from 'redux' import {show} from './modals' import {MODAL_ENVIRONMENT_EDITOR, MODAL_REQUEST_GROUP_RENAME} from '../../lib/constants' -const REQUEST_GROUP_UPDATE = 'request-groups/update'; -const REQUEST_GROUP_REPLACE = 'request-groups/replace'; -const REQUEST_GROUP_DELETE = 'request-groups/delete'; -const REQUEST_GROUP_TOGGLE = 'request-groups/toggle'; +export const REQUEST_GROUP_UPDATE = 'request-groups/update'; +export const REQUEST_GROUP_DELETE = 'request-groups/delete'; +export const REQUEST_GROUP_TOGGLE = 'request-groups/toggle'; // ~~~~~~~~ // // REDUCERS // @@ -23,9 +22,7 @@ function allReducer (state = [], action) { } else { return [...state.slice(0, i), action.requestGroup, ...state.slice(i + 1)] } - case REQUEST_GROUP_REPLACE: - return [...action.requestGroups]; - + case REQUEST_GROUP_TOGGLE: return state.map( rg => rg._id === action._id ? Object.assign({}, rg, {collapsed: !rg.collapsed}) : rg @@ -52,10 +49,6 @@ export function update (requestGroup) { return {type: REQUEST_GROUP_UPDATE, requestGroup}; } -export function replace (requestGroups) { - return {type: REQUEST_GROUP_REPLACE, requestGroups}; -} - export function remove (requestGroup) { return {type: REQUEST_GROUP_DELETE, requestGroup}; } diff --git a/app/redux/modules/requests.js b/app/redux/modules/requests.js index 7d57b28653..43a95f9690 100644 --- a/app/redux/modules/requests.js +++ b/app/redux/modules/requests.js @@ -5,10 +5,9 @@ import {loadStart, loadStop} from './global' import {show} from './modals' import {MODAL_REQUEST_RENAME} from '../../lib/constants' -const REQUEST_UPDATE = 'requests/update'; -const REQUEST_REPLACE = 'requests/replace'; -const REQUEST_DELETE = 'requests/delete'; -const REQUEST_CHANGE_FILTER = 'requests/filter'; +export const REQUEST_UPDATE = 'requests/update'; +export const REQUEST_DELETE = 'requests/delete'; +export const REQUEST_CHANGE_FILTER = 'requests/filter'; // ~~~~~~~~ // // REDUCERS // @@ -20,9 +19,6 @@ function allReducer (state = [], action) { case REQUEST_DELETE: return state.filter(r => r._id !== action.request._id); - case REQUEST_REPLACE: - return [...action.requests]; - case REQUEST_UPDATE: const i = state.findIndex(r => r._id === action.request._id); @@ -40,9 +36,6 @@ function allReducer (state = [], action) { function filterReducer (state = '', action) { switch (action.type) { - case REQUEST_REPLACE: - return ''; - case REQUEST_CHANGE_FILTER: return action.filter; @@ -54,15 +47,6 @@ function filterReducer (state = '', action) { function activeReducer (state = null, action) { switch (action.type) { - case REQUEST_REPLACE: - let newActive = action.requests.length ? action.requests[0] : null; - action.requests.map(w => { - if (w.activated > newActive.activated) { - newActive = w; - } - }); - return newActive ? Object.assign({}, newActive) : null; - case REQUEST_UPDATE: if (state && state._id === action.request._id) { // Update the currently active request @@ -104,10 +88,6 @@ export function update (request) { return {type: REQUEST_UPDATE, request}; } -export function replace (requests) { - return {type: REQUEST_REPLACE, requests}; -} - export function changeFilter (filter) { return {type: REQUEST_CHANGE_FILTER, filter}; } diff --git a/app/redux/modules/responses.js b/app/redux/modules/responses.js index 164694a9a1..91b280bea6 100644 --- a/app/redux/modules/responses.js +++ b/app/redux/modules/responses.js @@ -1,5 +1,5 @@ -const RESPONSE_UPDATE = 'responses/update'; -const RESPONSE_REPLACE = 'responses/replace'; +export const RESPONSE_UPDATE = 'responses/update'; +export const RESPONSE_DELETE = 'responses/delete'; const initialState = {}; @@ -10,13 +10,6 @@ const initialState = {}; export default function (state = initialState, action) { switch (action.type) { - case RESPONSE_REPLACE: - let newState = {}; - action.responses.map(r => { - newState[r.parentId] = r - }); - return newState; - case RESPONSE_UPDATE: return Object.assign({}, state, { [action.response.parentId]: action.response @@ -35,7 +28,3 @@ export default function (state = initialState, action) { export function update (response) { return {type: RESPONSE_UPDATE, response}; } - -export function replace (responses) { - return {type: RESPONSE_REPLACE, responses}; -} diff --git a/app/redux/modules/workspaces.js b/app/redux/modules/workspaces.js index 4ae1cca8a6..eb8af4d3b0 100644 --- a/app/redux/modules/workspaces.js +++ b/app/redux/modules/workspaces.js @@ -1,14 +1,11 @@ import {combineReducers} from 'redux' -import * as db from '../../database' -import {initStore} from '../initstore' import responsesReducer from './responses' import requestsReducer from './requests' import requestGroupsReducer from './requestGroups' -const WORKSPACE_UPDATE = 'workspaces/update'; -const WORKSPACE_DELETE = 'workspaces/delete'; -const WORKSPACE_REPLACE = 'workspaces/replace'; +export const WORKSPACE_UPDATE = 'workspaces/update'; +export const WORKSPACE_DELETE = 'workspaces/delete'; // ~~~~~~~~ // // REDUCERS // @@ -17,9 +14,6 @@ const WORKSPACE_REPLACE = 'workspaces/replace'; function allReducer (state = [], action) { switch (action.type) { - case WORKSPACE_REPLACE: - return [...action.workspaces]; - case WORKSPACE_UPDATE: const i = state.findIndex(w => w._id === action.workspace._id); @@ -44,15 +38,6 @@ function allReducer (state = [], action) { function activeReducer (state = null, action) { switch (action.type) { - case WORKSPACE_REPLACE: - let newActive = action.workspaces.length ? action.workspaces[0] : null; - action.workspaces.map(w => { - if (w.activated > newActive.activated) { - newActive = w; - } - }); - return newActive ? Object.assign({}, newActive) : null; - case WORKSPACE_UPDATE: if (state && state._id === action.workspace._id) { return action.workspace; @@ -112,15 +97,3 @@ export function remove (workspace) { export function update (workspace) { return {type: WORKSPACE_UPDATE, workspace}; } - -export function replace (workspaces) { - return {type: WORKSPACE_REPLACE, workspaces}; -} - -export function activate (workspace) { - return dispatch => { - db.workspaceActivate(workspace); - initStore(dispatch); - } -} - diff --git a/app/redux/reducer.js b/app/redux/reducer.js index 1015d11c3b..cb91d6a940 100644 --- a/app/redux/reducer.js +++ b/app/redux/reducer.js @@ -1,13 +1,15 @@ import {combineReducers} from 'redux' -import workspacesReducer from './modules/workspaces' -import tabsReducer from './modules/tabs' -import globalReducer from './modules/global' -import modalsReducer from './modules/modals' +import workspaces from './modules/workspaces' +import tabs from './modules/tabs' +import global from './modules/global' +import modals from './modules/modals' +import entities from './modules/entities' export default combineReducers({ - workspaces: workspacesReducer, - modals: modalsReducer, - global: globalReducer, - tabs: tabsReducer + workspaces, + modals, + global, + tabs, + entities }); From dcf9253eb5aea4db8032efa2c36efb8d1c67d059 Mon Sep 17 00:00:00 2001 From: Gregory Schier Date: Sun, 24 Apr 2016 20:55:43 -0700 Subject: [PATCH 06/13] Split up some components --- app/components/RequestBodyEditor.js | 20 +-- app/components/RequestPane.js | 132 +++++++++++++++++ app/components/RequestUrlBar.js | 33 +++-- app/components/ResponsePane.js | 91 ++++++++++++ app/components/Sidebar.js | 72 ++++----- app/components/SidebarRequestRow.js | 47 ++++++ app/containers/App.js | 222 ++++------------------------ app/database/index.js | 2 +- app/index.js | 4 + app/redux/modules/entities.js | 2 +- app/redux/reducer.js | 2 - 11 files changed, 361 insertions(+), 266 deletions(-) create mode 100644 app/components/RequestPane.js create mode 100644 app/components/ResponsePane.js create mode 100644 app/components/SidebarRequestRow.js diff --git a/app/components/RequestBodyEditor.js b/app/components/RequestBodyEditor.js index c9c09e16b0..a3e7165933 100644 --- a/app/components/RequestBodyEditor.js +++ b/app/components/RequestBodyEditor.js @@ -3,18 +3,17 @@ import Editor from './base/Editor' class RequestBodyEditor extends Component { render () { - const {request, onChange, className} = this.props; - const mode = request.contentType || 'text/plain'; + const {body, contentType, requestId, onChange, className} = this.props; return ( @@ -23,10 +22,13 @@ class RequestBodyEditor extends Component { } RequestBodyEditor.propTypes = { - request: PropTypes.shape({ - body: PropTypes.string.isRequired - }).isRequired, - onChange: PropTypes.func.isRequired + // Functions + onChange: PropTypes.func.isRequired, + + // Other + requestId: PropTypes.string.isRequired, + body: PropTypes.string.isRequired, + contentType: PropTypes.string.isRequired }; export default RequestBodyEditor; diff --git a/app/components/RequestPane.js b/app/components/RequestPane.js new file mode 100644 index 0000000000..f5350d3993 --- /dev/null +++ b/app/components/RequestPane.js @@ -0,0 +1,132 @@ +import React, {Component, PropTypes} from 'react' +import {Tab, Tabs, TabList, TabPanel} from 'react-tabs' + +import KeyValueEditor from '../components/base/KeyValueEditor' +import Dropdown from '../components/base/Dropdown' + +import RequestBodyEditor from '../components/RequestBodyEditor' +import RequestAuthEditor from '../components/RequestAuthEditor' +import RequestUrlBar from '../components/RequestUrlBar' + +class RequestPane extends Component { + render () { + const { + request, + sendRequest, + updateRequestUrl, + updateRequestMethod, + updateRequestBody, + updateRequestParams, + updateRequestAuthentication, + updateRequestHeaders + } = this.props; + + if (!request) { + return ( +
    +
    +
    +
    + ) + } + + return ( +
    +
    +
    + sendRequest(request)} + onUrlChange={updateRequestUrl} + onMethodChange={updateRequestMethod} + url={request.url} + method={request.method} + /> +
    + + + + + + +
      +
    • +
    • +
    • +
    • +
    +
    +
    + + + + + + +
    + + + + +
    + +
    +
    + +
    +
    + + +
    + + +
    +
    +
    +
    +
    +
    + ) + } +} + +RequestPane.propTypes = { + // Functions + sendRequest: PropTypes.func.isRequired, + updateRequestUrl: PropTypes.func.isRequired, + updateRequestMethod: PropTypes.func.isRequired, + updateRequestBody: PropTypes.func.isRequired, + updateRequestParams: PropTypes.func.isRequired, + updateRequestAuthentication: PropTypes.func.isRequired, + updateRequestHeaders: PropTypes.func.isRequired, + + // Other + request: PropTypes.object +}; + +export default RequestPane; diff --git a/app/components/RequestUrlBar.js b/app/components/RequestUrlBar.js index c21f616bc2..44aa46d8b9 100644 --- a/app/components/RequestUrlBar.js +++ b/app/components/RequestUrlBar.js @@ -4,36 +4,40 @@ import Dropdown from './base/Dropdown'; import {METHODS} from '../lib/constants'; class UrlInput extends Component { + _handleFormSubmit (e) { + e.preventDefault(); + this.props.sendRequest(); + } + render () { - const {sendRequest, onUrlChange, onMethodChange, request} = this.props; + const {onUrlChange, onMethodChange, uniquenessKey, url, method} = this.props; return (
      - {METHODS.map((method) => ( -
    • - + {METHODS.map(m => ( +
    • +
    • ))}
    {e.preventDefault(); sendRequest(request)}}> + onSubmit={this._handleFormSubmit.bind(this)}> -   
    @@ -45,10 +49,9 @@ UrlInput.propTypes = { sendRequest: PropTypes.func.isRequired, onUrlChange: PropTypes.func.isRequired, onMethodChange: PropTypes.func.isRequired, - request: PropTypes.shape({ - url: PropTypes.string.isRequired, - method: PropTypes.string.isRequired - }).isRequired + uniquenessKey: PropTypes.string.isRequired, + url: PropTypes.string.isRequired, + method: PropTypes.string.isRequired }; export default UrlInput; diff --git a/app/components/ResponsePane.js b/app/components/ResponsePane.js new file mode 100644 index 0000000000..5011b9f895 --- /dev/null +++ b/app/components/ResponsePane.js @@ -0,0 +1,91 @@ +import React, {Component, PropTypes} from 'react' +import {Tab, Tabs, TabList, TabPanel} from 'react-tabs' + +import StatusTag from '../components/StatusTag' +import Editor from '../components/base/Editor' +import SizeTag from '../components/SizeTag' +import TimeTag from '../components/TimeTag' + +class ResponsePane extends Component { + render () { + const { + response + } = this.props; + + if (!response) { + return ( +
    +
    +
    +
    + ) + } + + return ( +
    +
    +
    + {!response ? null : ( +
    + + + +
    + )} +
    + + + + + + + + + + + + + +
    +
    + {!response ? null : response.headers.map((h, i) => ( +
    +
    {h.name}
    +
    {h.value}
    +
    + ))} +
    +
    +
    +
    +
    +
    + ) + } +} + +ResponsePane.propTypes = { + response: PropTypes.object +}; + +export default ResponsePane; diff --git a/app/components/Sidebar.js b/app/components/Sidebar.js index e492fc1244..e336bbe056 100644 --- a/app/components/Sidebar.js +++ b/app/components/Sidebar.js @@ -1,10 +1,9 @@ import React, {Component, PropTypes} from 'react' import classnames from 'classnames' import WorkspaceDropdown from './../containers/WorkspaceDropdown' -import RequestActionsDropdown from './../containers/RequestActionsDropdown' import RequestGroupActionsDropdown from './../containers/RequestGroupActionsDropdown' import DebouncingInput from './base/DebouncingInput' -import MethodTag from './MethodTag' +import SidebarRequestRow from './SidebarRequestRow' class Sidebar extends Component { onFilterChange (value) { @@ -13,25 +12,25 @@ class Sidebar extends Component { renderRequestGroupRow (requestGroup = null) { const { - activeFilter, - activeRequest, + filter, + activeRequestId, addRequestToRequestGroup, toggleRequestGroup, requests, - workspace + workspaceId } = this.props; let filteredRequests = requests.filter( r => { // TODO: Move this to a lib file - if (!activeFilter) { + if (!filter) { return true; } const requestGroupName = requestGroup ? requestGroup.name : ''; const toMatch = `${requestGroupName}✌${r.method}✌${r.name}`.toLowerCase(); - const matchTokens = activeFilter.toLowerCase().split(' '); + const matchTokens = filter.toLowerCase().split(' '); for (let i = 0; i < matchTokens.length; i++) { let token = `${matchTokens[i]}`; if (toMatch.indexOf(token) === -1) { @@ -44,7 +43,7 @@ class Sidebar extends Component { ); if (!requestGroup) { - filteredRequests = filteredRequests.filter(r => r.parentId === workspace._id); + filteredRequests = filteredRequests.filter(r => r.parentId === workspaceId); return filteredRequests.map(request => this.renderRequestRow(request)); } @@ -52,11 +51,11 @@ class Sidebar extends Component { filteredRequests = filteredRequests.filter(r => r.parentId === requestGroup._id); // Don't show folder if it was not in the filter - if (activeFilter && !filteredRequests.length) { + if (filter && !filteredRequests.length) { return null; } - const isActive = activeRequest && filteredRequests.find(r => r._id == activeRequest._id); + const isActive = activeRequestId && filteredRequests.find(r => r._id == activeRequestId); let folderIconClass = 'fa-folder'; let expanded = !requestGroup.collapsed; @@ -97,36 +96,22 @@ class Sidebar extends Component { } renderRequestRow (request = null, requestGroup = null) { - const {activeRequest, activateRequest} = this.props; - const isActive = request && activeRequest && request._id === activeRequest._id; - + const {activeRequestId, activateRequest} = this.props; + const isActive = request && activeRequestId && request._id === activeRequestId; + return ( -
  • -
    -
    - {request ? ( - - ) : ( - - )} -
    - {request ? ( - - ) : null} -
    -
  • - ); + + ) } render () { - const {activeFilter, requestGroups} = this.props; + const {filter, requestGroups} = this.props; return (
    @@ -145,7 +130,7 @@ class Sidebar extends Component { type="text" placeholder="Filter Items" debounceMillis={300} - value={activeFilter} + value={filter} onChange={this.onFilterChange.bind(this)}/> @@ -156,15 +141,20 @@ class Sidebar extends Component { } Sidebar.propTypes = { + // Functions activateRequest: PropTypes.func.isRequired, + toggleRequestGroup: PropTypes.func.isRequired, addRequestToRequestGroup: PropTypes.func.isRequired, changeFilter: PropTypes.func.isRequired, - toggleRequestGroup: PropTypes.func.isRequired, - activeFilter: PropTypes.string, + + // Other requests: PropTypes.array.isRequired, requestGroups: PropTypes.array.isRequired, - workspace: PropTypes.object.isRequired, - activeRequest: PropTypes.object + workspaceId: PropTypes.string.isRequired, + + // Optional + filter: PropTypes.string, + activeRequestId: PropTypes.string }; export default Sidebar; diff --git a/app/components/SidebarRequestRow.js b/app/components/SidebarRequestRow.js new file mode 100644 index 0000000000..4ddb4b97fd --- /dev/null +++ b/app/components/SidebarRequestRow.js @@ -0,0 +1,47 @@ +import React, {Component, PropTypes} from 'react' +import RequestActionsDropdown from './../containers/RequestActionsDropdown' +import MethodTag from './MethodTag' + +class SidebarRequestRow extends Component { + render () { + const {request, requestGroup, isActive, activateRequest} = this.props; + + return ( +
  • +
    +
    + {request ? ( + + ) : ( + + )} +
    + {request ? ( + + ) : null} +
    +
  • + ); + } +} + +SidebarRequestRow.propTypes = { + // Functions + activateRequest: PropTypes.func.isRequired, + + // Other + request: PropTypes.object.isRequired, + isActive: PropTypes.bool.isRequired, + + // Optional + requestGroup: PropTypes.object +}; + +export default SidebarRequestRow; diff --git a/app/containers/App.js b/app/containers/App.js index 894fd0462b..0d6e61157b 100644 --- a/app/containers/App.js +++ b/app/containers/App.js @@ -1,32 +1,20 @@ import React, {Component, PropTypes} from 'react' import {connect} from 'react-redux' import {bindActionCreators} from 'redux' -import {Tab, Tabs, TabList, TabPanel} from 'react-tabs' -import Editor from '../components/base/Editor' import Prompts from './Prompts' -import KeyValueEditor from '../components/base/KeyValueEditor' -import Dropdown from '../components/base/Dropdown' -import RequestBodyEditor from '../components/RequestBodyEditor' -import RequestAuthEditor from '../components/RequestAuthEditor' -import RequestUrlBar from '../components/RequestUrlBar' -import StatusTag from '../components/StatusTag' -import SizeTag from '../components/SizeTag' -import TimeTag from '../components/TimeTag' -import Sidebar from '../components/Sidebar' import EnvironmentEditModal from '../components/EnvironmentEditModal' +import RequestPane from '../components/RequestPane' +import ResponsePane from '../components/ResponsePane' +import Sidebar from '../components/Sidebar' import * as GlobalActions from '../redux/modules/global' import * as RequestGroupActions from '../redux/modules/requestGroups' import * as RequestActions from '../redux/modules/requests' import * as ModalActions from '../redux/modules/modals' -import * as TabActions from '../redux/modules/tabs' import * as db from '../database' -// Don't inject component styles (use our own) -Tabs.setUseDefaultStyles(false); - class App extends Component { constructor (props) { super(props); @@ -36,194 +24,44 @@ class App extends Component { } } - _renderRequestPanel (actions, activeRequest, tabs) { - if (!activeRequest) { - return ( -
    -
    -
    -
    - ) - } - - return ( -
    -
    -
    - {db.update(activeRequest, {url})}} - onMethodChange={method => {db.update(activeRequest, {method})}} - request={activeRequest} - /> -
    - actions.tabs.select('request', i)} - selectedIndex={tabs.request || 0}> - - - - - -
      -
    • -
    • -
    • -
    • -
    -
    -
    - - - - - - -
    - - {db.update(activeRequest, {body})}} - request={activeRequest}/> - - -
    - {db.update(activeRequest, {params})}} - /> -
    -
    - -
    -
    - - {db.update(activeRequest, {authentication})}} - /> -
    - - {db.update(activeRequest, {headers})}} - /> -
    -
    -
    -
    -
    -
    - ) - } - - _renderResponsePanel (actions, activeResponse, tabs) { - if (!activeResponse) { - return ( -
    -
    -
    -
    - ) - } - - return ( -
    -
    -
    - {!activeResponse ? null : ( -
    - - - -
    - )} -
    - actions.tabs.select('response', i)} - selectedIndex={tabs.response || 0}> - - - - - - - - - - - - -
    -
    - {!activeResponse ? null : activeResponse.headers.map((h, i) => ( -
    -
    {h.name}
    -
    {h.value}
    -
    - ))} -
    -
    -
    -
    -
    -
    - ) - } - render () { - const {actions, tabs, modals, workspaces} = this.props; + const {actions, modals, workspaces} = this.props; const {requests, requestGroups, responses} = workspaces; + const activeRequest = requests.active; const activeResponse = activeRequest ? responses[activeRequest._id] : undefined; return (
    db.requestCreate({parentId: requestGroup._id})} toggleRequestGroup={requestGroup => db.update(requestGroup, {collapsed: !requestGroup.collapsed})} - activeRequest={activeRequest} - activeFilter={requests.filter} + activeRequestId={activeRequest._id} + filter={requests.filter} requestGroups={requestGroups.all.sort((a, b) => a._id > b._id ? -1 : 1)} - requests={requests.all.sort((a, b) => a._id > b._id ? -1 : 1)}/> + requests={requests.all.sort((a, b) => a._id > b._id ? -1 : 1)} + />
    - {this._renderRequestPanel(actions, activeRequest, tabs)} - {this._renderResponsePanel(actions, activeResponse, tabs)} + db.update(activeRequest, {body})} + updateRequestUrl={url => db.update(activeRequest, {url})} + updateRequestMethod={method => db.update(activeRequest, {method})} + updateRequestParams={params => db.update(activeRequest, {params})} + updateRequestAuthentication={authentication => db.update(activeRequest, {authentication})} + updateRequestHeaders={headers => db.update(activeRequest, {headers})} + /> +
    + + {modals.map(m => { if (m.id === EnvironmentEditModal.defaultProps.id) { return ( @@ -246,21 +84,14 @@ class App extends Component { App.propTypes = { actions: PropTypes.shape({ requests: PropTypes.shape({ - update: PropTypes.func.isRequired, - remove: PropTypes.func.isRequired, send: PropTypes.func.isRequired, changeFilter: PropTypes.func.isRequired }), requestGroups: PropTypes.shape({ - remove: PropTypes.func.isRequired, - update: PropTypes.func.isRequired, toggle: PropTypes.func.isRequired }), modals: PropTypes.shape({ hide: PropTypes.func.isRequired - }), - tabs: PropTypes.shape({ - select: PropTypes.func.isRequired }) }).isRequired, workspaces: PropTypes.shape({ @@ -274,7 +105,6 @@ App.propTypes = { active: PropTypes.object }).isRequired }).isRequired, - tabs: PropTypes.object.isRequired, modals: PropTypes.array.isRequired }; @@ -282,7 +112,6 @@ function mapStateToProps (state) { return { actions: state.actions, workspaces: state.workspaces, - tabs: state.tabs, modals: state.modals }; } @@ -291,7 +120,6 @@ function mapDispatchToProps (dispatch) { return { actions: { global: bindActionCreators(GlobalActions, dispatch), - tabs: bindActionCreators(TabActions, dispatch), modals: bindActionCreators(ModalActions, dispatch), requestGroups: bindActionCreators(RequestGroupActions, dispatch), requests: bindActionCreators(RequestActions, dispatch) diff --git a/app/database/index.js b/app/database/index.js index 320c4686c5..df8c5ba6b5 100644 --- a/app/database/index.js +++ b/app/database/index.js @@ -13,7 +13,7 @@ export const TYPE_RESPONSE = 'Response'; let db = new PouchDB('insomnia.db', {adapter: 'websql'}); // For browser console debugging -global.db = db; +// global.db = db; let changeListeners = {}; diff --git a/app/index.js b/app/index.js index 4c097a008f..484af0ac2e 100644 --- a/app/index.js +++ b/app/index.js @@ -1,6 +1,7 @@ import React from 'react' import {render} from 'react-dom' import {Provider} from 'react-redux' +import {Tabs} from 'react-tabs' import createStore from './redux/create' import App from './containers/App' @@ -12,6 +13,9 @@ import './css/lib/fontawesome/css/font-awesome.css' import {initStore} from './redux/initstore' import {initDB} from './database' +// Don't inject component styles (use our own) +Tabs.setUseDefaultStyles(false); + export const store = createStore(); console.log('-- Loading App --'); diff --git a/app/redux/modules/entities.js b/app/redux/modules/entities.js index 83276b7efa..770a83ea12 100644 --- a/app/redux/modules/entities.js +++ b/app/redux/modules/entities.js @@ -37,7 +37,7 @@ const workspaces = generateEntityReducer( ); const requestGroups = generateEntityReducer( - 'requestGroup', + 'requestGroup', requestGroupFns.REQUEST_GROUP_UPDATE, requestGroupFns.REQUEST_GROUP_DELETE ); diff --git a/app/redux/reducer.js b/app/redux/reducer.js index cb91d6a940..d0ed39b34a 100644 --- a/app/redux/reducer.js +++ b/app/redux/reducer.js @@ -1,7 +1,6 @@ import {combineReducers} from 'redux' import workspaces from './modules/workspaces' -import tabs from './modules/tabs' import global from './modules/global' import modals from './modules/modals' import entities from './modules/entities' @@ -10,6 +9,5 @@ export default combineReducers({ workspaces, modals, global, - tabs, entities }); From 775a623c80c7cac590ca56bc6efb7c4e6fec0aae Mon Sep 17 00:00:00 2001 From: Gregory Schier Date: Sun, 24 Apr 2016 21:01:36 -0700 Subject: [PATCH 07/13] Moved nested modules back out of workspaces --- app/containers/App.js | 24 +++++++++++++----------- app/redux/modules/workspaces.js | 33 ++------------------------------- app/redux/reducer.js | 6 ++++++ 3 files changed, 21 insertions(+), 42 deletions(-) diff --git a/app/containers/App.js b/app/containers/App.js index 0d6e61157b..c709efcc44 100644 --- a/app/containers/App.js +++ b/app/containers/App.js @@ -25,8 +25,7 @@ class App extends Component { } render () { - const {actions, modals, workspaces} = this.props; - const {requests, requestGroups, responses} = workspaces; + const {actions, modals, workspaces, requestGroups, requests, responses} = this.props; const activeRequest = requests.active; const activeResponse = activeRequest ? responses[activeRequest._id] : undefined; @@ -95,15 +94,15 @@ App.propTypes = { }) }).isRequired, workspaces: PropTypes.shape({ - active: PropTypes.object, - responses: PropTypes.object.isRequired, - requestGroups: PropTypes.shape({ - all: PropTypes.array.isRequired - }).isRequired, - requests: PropTypes.shape({ - all: PropTypes.array.isRequired, - active: PropTypes.object - }).isRequired + active: PropTypes.object + }).isRequired, + responses: PropTypes.object.isRequired, + requestGroups: PropTypes.shape({ + all: PropTypes.array.isRequired + }).isRequired, + requests: PropTypes.shape({ + all: PropTypes.array.isRequired, + active: PropTypes.object }).isRequired, modals: PropTypes.array.isRequired }; @@ -112,6 +111,9 @@ function mapStateToProps (state) { return { actions: state.actions, workspaces: state.workspaces, + requestGroups: state.requestGroups, + requests: state.requests, + responses: state.responses, modals: state.modals }; } diff --git a/app/redux/modules/workspaces.js b/app/redux/modules/workspaces.js index eb8af4d3b0..325284721f 100644 --- a/app/redux/modules/workspaces.js +++ b/app/redux/modules/workspaces.js @@ -1,9 +1,5 @@ import {combineReducers} from 'redux' -import responsesReducer from './responses' -import requestsReducer from './requests' -import requestGroupsReducer from './requestGroups' - export const WORKSPACE_UPDATE = 'workspaces/update'; export const WORKSPACE_DELETE = 'workspaces/delete'; @@ -55,36 +51,11 @@ function activeReducer (state = null, action) { } } -const workspaceReducer = combineReducers({ +export default combineReducers({ all: allReducer, - active: activeReducer, - - // Nested resources - responses: responsesReducer, - requests: requestsReducer, - requestGroups: requestGroupsReducer + active: activeReducer }); -export default function (state = {}, action) { - // Call the reducer manually, so we can check if the active workspace has changed - let newState = workspaceReducer(state, action); - - const oldActiveId = state.active && state.active._id; - const newActiveId = newState.active && newState.active._id; - const activeWorkspaceChanged = oldActiveId !== newActiveId; - - // Clear all of these things if the workspace has changed - if (activeWorkspaceChanged) { - newState = Object.assign({}, newState, { - responses: responsesReducer(undefined, action), - requests: requestsReducer(undefined, action), - requestGroups: requestGroupsReducer(undefined, action) - }) - } - - return newState; -} - // ~~~~~~~ // // ACTIONS // diff --git a/app/redux/reducer.js b/app/redux/reducer.js index d0ed39b34a..49a1d27824 100644 --- a/app/redux/reducer.js +++ b/app/redux/reducer.js @@ -1,12 +1,18 @@ import {combineReducers} from 'redux' import workspaces from './modules/workspaces' +import requestGroups from './modules/requestGroups' +import requests from './modules/requests' +import responses from './modules/responses' import global from './modules/global' import modals from './modules/modals' import entities from './modules/entities' export default combineReducers({ workspaces, + responses, + requests, + requestGroups, modals, global, entities From 410a218573d36cffcff0986f0f8a7b88a0841b18 Mon Sep 17 00:00:00 2001 From: Gregory Schier Date: Sun, 24 Apr 2016 22:50:42 -0700 Subject: [PATCH 08/13] Started on new Sidebar tree stuff --- app/components/Sidebar.js | 93 ++++++++++++++--------------- app/components/SidebarRequestRow.js | 4 +- app/containers/App.js | 49 ++++++++++----- app/containers/Prompts.js | 12 +--- app/database/index.js | 6 +- app/redux/modules/entities.js | 70 +++++++++++----------- app/redux/modules/requests.js | 72 +++------------------- app/redux/modules/responses.js | 22 +------ 8 files changed, 127 insertions(+), 201 deletions(-) diff --git a/app/components/Sidebar.js b/app/components/Sidebar.js index e336bbe056..09ebd0ce77 100644 --- a/app/components/Sidebar.js +++ b/app/components/Sidebar.js @@ -10,52 +10,26 @@ class Sidebar extends Component { this.props.changeFilter(value); } - renderRequestGroupRow (requestGroup = null) { + renderRequestGroupRow (child, parent) { const { filter, activeRequestId, addRequestToRequestGroup, - toggleRequestGroup, - requests, - workspaceId + toggleRequestGroup } = this.props; - - let filteredRequests = requests.filter( - r => { - // TODO: Move this to a lib file - - if (!filter) { - return true; - } - - const requestGroupName = requestGroup ? requestGroup.name : ''; - const toMatch = `${requestGroupName}✌${r.method}✌${r.name}`.toLowerCase(); - const matchTokens = filter.toLowerCase().split(' '); - for (let i = 0; i < matchTokens.length; i++) { - let token = `${matchTokens[i]}`; - if (toMatch.indexOf(token) === -1) { - return false; - } - } - - return true; - } - ); + + const requestGroup = child.doc.type === 'RequestGroup' ? child.doc : null; if (!requestGroup) { - filteredRequests = filteredRequests.filter(r => r.parentId === workspaceId); - return filteredRequests.map(request => this.renderRequestRow(request)); + return child.children.map(c => this._renderChild(c, child)); } - // Grab all of the children for this request group - filteredRequests = filteredRequests.filter(r => r.parentId === requestGroup._id); - // Don't show folder if it was not in the filter - if (filter && !filteredRequests.length) { + if (filter && !child.children.length) { return null; } - const isActive = activeRequestId && filteredRequests.find(r => r._id == activeRequestId); + const isActive = activeRequestId && child.children.find(c => c.doc._id == activeRequestId); let folderIconClass = 'fa-folder'; let expanded = !requestGroup.collapsed; @@ -68,6 +42,8 @@ class Sidebar extends Component { {'sidebar__item--active': isActive} ); + child.children.sort((a, b) => a.doc._id > b.doc._id ? -1 : 1); + return (
  • @@ -88,20 +64,22 @@ class Sidebar extends Component {
    • - {expanded && !filteredRequests.length ? this.renderRequestRow() : null} - {!expanded ? null : filteredRequests.map(request => this.renderRequestRow(request, requestGroup))} + {expanded && !child.children.length ? this.renderRequestRow() : null} + {!expanded ? null : child.children.map(c => this._renderChild(c, child))}
    ); } - renderRequestRow (request = null, requestGroup = null) { + renderRequestRow (child = null, parent = null) { + const request = child ? child.doc : null; + const requestGroup = parent ? parent.doc : null; const {activeRequestId, activateRequest} = this.props; - const isActive = request && activeRequestId && request._id === activeRequestId; - + const isActive = request && activeRequestId && request._id === activeRequestId || false; + return (
    @@ -121,8 +122,7 @@ class Sidebar extends Component {
      - {this.renderRequestGroupRow(null)} - {requestGroups.map(requestGroup => this.renderRequestGroupRow(requestGroup))} + {children.map(c => this._renderChild(c))}
    @@ -146,12 +146,11 @@ Sidebar.propTypes = { toggleRequestGroup: PropTypes.func.isRequired, addRequestToRequestGroup: PropTypes.func.isRequired, changeFilter: PropTypes.func.isRequired, - + // Other - requests: PropTypes.array.isRequired, - requestGroups: PropTypes.array.isRequired, + children: PropTypes.array.isRequired, workspaceId: PropTypes.string.isRequired, - + // Optional filter: PropTypes.string, activeRequestId: PropTypes.string diff --git a/app/components/SidebarRequestRow.js b/app/components/SidebarRequestRow.js index 4ddb4b97fd..20481e9531 100644 --- a/app/components/SidebarRequestRow.js +++ b/app/components/SidebarRequestRow.js @@ -37,11 +37,11 @@ SidebarRequestRow.propTypes = { activateRequest: PropTypes.func.isRequired, // Other - request: PropTypes.object.isRequired, isActive: PropTypes.bool.isRequired, // Optional - requestGroup: PropTypes.object + requestGroup: PropTypes.object, + request: PropTypes.object }; export default SidebarRequestRow; diff --git a/app/containers/App.js b/app/containers/App.js index c709efcc44..af65a725c3 100644 --- a/app/containers/App.js +++ b/app/containers/App.js @@ -24,24 +24,47 @@ class App extends Component { } } - render () { - const {actions, modals, workspaces, requestGroups, requests, responses} = this.props; + _generateSidebarTree (parentId, entities) { + const children = entities.filter(e => e.parentId === parentId); + + if (children.length > 0) { + return children.map(c => ({ + doc: c, + children: this._generateSidebarTree(c._id, entities) + })); + } else { + return children; + } + } - const activeRequest = requests.active; - const activeResponse = activeRequest ? responses[activeRequest._id] : undefined; + render () { + const {actions, modals, workspaces, requests, entities} = this.props; + + const activeRequestId = workspaces.active.activeRequestId; + const activeRequest = activeRequestId ? entities.requests[activeRequestId] : null; + + const responses = Object.keys(entities.responses).map(id => entities.responses[id]); + const allRequests = Object.keys(entities.requests).map(id => entities.requests[id]); + const allRequestGroups = Object.keys(entities.requestGroups).map(id => entities.requestGroups[id]); + + const activeResponse = responses.find(r => r.parentId === activeRequestId); + + const children = this._generateSidebarTree( + workspaces.active._id, + allRequests.concat(allRequestGroups) + ); return (
    db.update(workspaces.active, {activeRequestId: r._id})} changeFilter={actions.requests.changeFilter} addRequestToRequestGroup={requestGroup => db.requestCreate({parentId: requestGroup._id})} toggleRequestGroup={requestGroup => db.update(requestGroup, {collapsed: !requestGroup.collapsed})} - activeRequestId={activeRequest._id} + activeRequestId={activeRequest ? activeRequest._id : null} filter={requests.filter} - requestGroups={requestGroups.all.sort((a, b) => a._id > b._id ? -1 : 1)} - requests={requests.all.sort((a, b) => a._id > b._id ? -1 : 1)} + children={children} />
    ({type: ENTITY_UPDATE, response}), + [TYPE_REQUEST]: request => ({type: ENTITY_UPDATE, request}) }; const removeFns = { [TYPE_WORKSPACE]: workspaceFns.remove, [TYPE_REQUEST_GROUP]: requestGroupFns.remove, - [TYPE_REQUEST]: requestFns.remove - // Response doesn't have a remove function (yet...) + [TYPE_RESPONSE]: response => ({type: ENTITY_UPDATE, response}), + [TYPE_REQUEST]: request => ({type: ENTITY_REMOVE, request}) }; export function update (doc) { - // Using dispatch here because Redux gets mad if we don't return anything - // in a normal action creator - return dispatch => { - if (updateFns.hasOwnProperty(doc.type)) { - dispatch(updateFns[doc.type](doc)); - } - } + return updateFns[doc.type](doc); } export function remove (doc) { - // Using dispatch here because Redux gets mad if we don't return anything - // in a normal action creator - return dispatch => { - if (removeFns.hasOwnProperty(doc.type)) { - dispatch(removeFns[doc.type](doc)); - } - } + return removeFns[doc.type](doc); } diff --git a/app/redux/modules/requests.js b/app/redux/modules/requests.js index 43a95f9690..9d720f01c6 100644 --- a/app/redux/modules/requests.js +++ b/app/redux/modules/requests.js @@ -1,93 +1,35 @@ -import {combineReducers} from 'redux' - import * as network from '../../lib/network' import {loadStart, loadStop} from './global' import {show} from './modals' import {MODAL_REQUEST_RENAME} from '../../lib/constants' -export const REQUEST_UPDATE = 'requests/update'; -export const REQUEST_DELETE = 'requests/delete'; export const REQUEST_CHANGE_FILTER = 'requests/filter'; +const initialState = { + filter: '' +}; + // ~~~~~~~~ // // REDUCERS // // ~~~~~~~~ // -function allReducer (state = [], action) { - switch (action.type) { - - case REQUEST_DELETE: - return state.filter(r => r._id !== action.request._id); - - case REQUEST_UPDATE: - const i = state.findIndex(r => r._id === action.request._id); - - if (i === -1) { - return [action.request, ...state]; - } else { - return [...state.slice(0, i), action.request, ...state.slice(i + 1)] - } - - default: - return state; - } -} - -function filterReducer (state = '', action) { +export default function (state = initialState, action) { switch (action.type) { case REQUEST_CHANGE_FILTER: - return action.filter; + const filter = action.filter; + return Object.assign({}, state, {filter}); default: return state; } } -function activeReducer (state = null, action) { - switch (action.type) { - - case REQUEST_UPDATE: - if (state && state._id === action.request._id) { - // Update the currently active request - return action.request; - } else if (state) { - // Activate a new request - return action.request.activated > state.activated ? action.request : state; - } else if (action.request.activated > 0) { - // Activate request if there isn't one - return action.request; - } else { - return state; - } - - case REQUEST_DELETE: - return state && state._id === action.request._id ? null : state; - - default: - return state; - } -} - -export default combineReducers({ - all: allReducer, - filter: filterReducer, - active: activeReducer -}); - // ~~~~~~~ // // ACTIONS // // ~~~~~~~ // -export function remove (request) { - return {type: REQUEST_DELETE, request}; -} - -export function update (request) { - return {type: REQUEST_UPDATE, request}; -} - export function changeFilter (filter) { return {type: REQUEST_CHANGE_FILTER, filter}; } diff --git a/app/redux/modules/responses.js b/app/redux/modules/responses.js index 91b280bea6..f3375a8922 100644 --- a/app/redux/modules/responses.js +++ b/app/redux/modules/responses.js @@ -1,30 +1,12 @@ -export const RESPONSE_UPDATE = 'responses/update'; -export const RESPONSE_DELETE = 'responses/delete'; - -const initialState = {}; - // ~~~~~~~~ // // REDUCERS // // ~~~~~~~~ // -export default function (state = initialState, action) { - switch (action.type) { - - case RESPONSE_UPDATE: - return Object.assign({}, state, { - [action.response.parentId]: action.response - }); - - default: - return state; - } -} +// None yet // ~~~~~~~ // // ACTIONS // // ~~~~~~~ // -export function update (response) { - return {type: RESPONSE_UPDATE, response}; -} +// None yet... From 5b12b3d6984d490d113780f88198f6685e8b53e4 Mon Sep 17 00:00:00 2001 From: Gregory Schier Date: Sun, 24 Apr 2016 23:23:26 -0700 Subject: [PATCH 09/13] Better store stuff --- app/containers/RequestGroupActionsDropdown.js | 2 -- app/database/index.js | 23 +++++++++++++++---- app/lib/import.js | 20 ++++++++-------- app/redux/initstore.js | 23 +++++++++---------- app/redux/modules/entities.js | 13 +++-------- app/redux/modules/requestGroups.js | 22 ------------------ 6 files changed, 42 insertions(+), 61 deletions(-) diff --git a/app/containers/RequestGroupActionsDropdown.js b/app/containers/RequestGroupActionsDropdown.js index 1ae002b371..a950282fdc 100644 --- a/app/containers/RequestGroupActionsDropdown.js +++ b/app/containers/RequestGroupActionsDropdown.js @@ -38,8 +38,6 @@ class RequestGroupActionsDropdown extends Component { RequestGroupActionsDropdown.propTypes = { actions: PropTypes.shape({ - update: PropTypes.func.isRequired, - remove: PropTypes.func.isRequired, showUpdateNamePrompt: PropTypes.func.isRequired, showEnvironmentEditModal: PropTypes.func.isRequired }), diff --git a/app/database/index.js b/app/database/index.js index 7958267b1b..9bb5d51bc1 100644 --- a/app/database/index.js +++ b/app/database/index.js @@ -27,6 +27,10 @@ export function offChange (id) { delete changeListeners[id]; } +export function allDocs () { + return db.allDocs({include_docs: true}); +} + db.changes({ since: 'now', live: true, @@ -48,8 +52,7 @@ export function initDB () { console.log('-- Initializing Database --'); return Promise.all([ db.createIndex({index: {fields: ['parentId']}}), - db.createIndex({index: {fields: ['type']}}), - db.createIndex({index: {fields: ['activated', 'type']}}) + db.createIndex({index: {fields: ['type']}}) ]).catch(err => { console.error('Failed to PouchDB Indexes', err); }); @@ -103,7 +106,7 @@ function modelCreate (type, idPrefix, defaults, patch = {}) { parentId: null }; - const model = Object.assign( + const doc = Object.assign( baseDefaults, defaults, patch, @@ -118,8 +121,7 @@ function modelCreate (type, idPrefix, defaults, patch = {}) { } ); - update(model); - return model; + return update(doc).then(() => doc); } // ~~~~~~~ // @@ -195,6 +197,17 @@ export function workspaceActivate (workspace) { export function workspaceAll () { return db.find({ selector: {type: 'Workspace'} + }).then(res => { + if (res.docs.length) { + return res; + } else { + // No workspaces? Create first one and try again + // TODO: Replace this with UI flow maybe? + console.log('-- Creating First Workspace --'); + return workspaceCreate({name: 'First Workspace'}).then(() => { + return workspaceAll(); + }) + } }) } diff --git a/app/lib/import.js b/app/lib/import.js index ec12f5ead6..d70496f7a9 100644 --- a/app/lib/import.js +++ b/app/lib/import.js @@ -9,21 +9,21 @@ const FORMAT_MAP = { function importRequestGroup (iRequestGroup, parentId, exportFormat) { if (exportFormat === 1) { - const requestGroup = db.requestGroupCreate({ + db.requestGroupCreate({ parentId, collapsed: true, name: iRequestGroup.name, environment: (iRequestGroup.environments || {}).base || {} + }).then(requestGroup => { + // Sometimes (maybe all the time, I can't remember) requests will be nested + if (iRequestGroup.hasOwnProperty('requests')) { + // Let's process them oldest to newest + iRequestGroup.requests.reverse(); + iRequestGroup.requests.map( + r => importRequest(r, requestGroup._id, exportFormat) + ); + } }); - - // Sometimes (maybe all the time, I can't remember) requests will be nested - if (iRequestGroup.hasOwnProperty('requests')) { - // Let's process them oldest to newest - iRequestGroup.requests.reverse(); - iRequestGroup.requests.map( - r => importRequest(r, requestGroup._id, exportFormat) - ); - } } } diff --git a/app/redux/initstore.js b/app/redux/initstore.js index 0555c23130..0ad9fc654b 100644 --- a/app/redux/initstore.js +++ b/app/redux/initstore.js @@ -6,7 +6,7 @@ const CHANGE_ID = 'store.listener'; export function initStore (dispatch) { db.offChange(CHANGE_ID); - + // New stuff... const entities = bindActionCreators(entitiesActions, dispatch); @@ -14,7 +14,7 @@ export function initStore (dispatch) { if (!doc.hasOwnProperty('type')) { return; } - + // New stuff... entities[doc._deleted ? 'remove' : 'update'](doc); }; @@ -22,28 +22,27 @@ export function initStore (dispatch) { console.log('-- Restoring Store --'); const start = Date.now(); - return db.workspaceAll().then(res => { - res.docs.map(docChanged); - }).then(() => { - return db.workspaceGetActive() - }).then(res => { - const workspace = res.docs.length ? res.docs[0] : db.workspaceCreate(); - const restoreChildren = (doc, maxDepth, depth = 0) => { + return db.workspaceAll().then(res => { + const restoreChildren = (doc) => { docChanged(doc); return db.getChildren(doc).then(res => { + // Done condition + if (!res.docs.length) { + return; + } + return Promise.all( - res.docs.map(doc => restoreChildren(doc, maxDepth, depth++)) + res.docs.map(doc => restoreChildren(doc)) ); }) }; - return restoreChildren(workspace, 5); + return res.docs.map(restoreChildren) }).then(() => { console.log(`Restore took ${(Date.now() - start) / 1000} s`); }).then(() => { db.onChange(CHANGE_ID, res => docChanged(res.doc)); }); } - diff --git a/app/redux/modules/entities.js b/app/redux/modules/entities.js index 8d2a5a6fb3..bc7563e313 100644 --- a/app/redux/modules/entities.js +++ b/app/redux/modules/entities.js @@ -2,7 +2,6 @@ import {combineReducers} from 'redux' import {TYPE_WORKSPACE, TYPE_REQUEST_GROUP, TYPE_REQUEST, TYPE_RESPONSE} from '../../database/index' import * as workspaceFns from './workspaces' -import * as requestGroupFns from './requestGroups' const ENTITY_UPDATE = 'entities/update'; const ENTITY_REMOVE = 'entities/remove'; @@ -60,15 +59,9 @@ const workspaces = generateEntityReducer( workspaceFns.WORKSPACE_DELETE ); -const requestGroups = generateEntityReducer( - 'requestGroup', - requestGroupFns.REQUEST_GROUP_UPDATE, - requestGroupFns.REQUEST_GROUP_DELETE -); - export default combineReducers({ workspaces, - requestGroups, + requestGroups: genericEntityReducer('requestGroup'), requests: genericEntityReducer('request'), responses: genericEntityReducer('response') }) @@ -80,14 +73,14 @@ export default combineReducers({ const updateFns = { [TYPE_WORKSPACE]: workspaceFns.update, - [TYPE_REQUEST_GROUP]: requestGroupFns.update, + [TYPE_REQUEST_GROUP]: requestGroup => ({type: ENTITY_UPDATE, requestGroup}), [TYPE_RESPONSE]: response => ({type: ENTITY_UPDATE, response}), [TYPE_REQUEST]: request => ({type: ENTITY_UPDATE, request}) }; const removeFns = { [TYPE_WORKSPACE]: workspaceFns.remove, - [TYPE_REQUEST_GROUP]: requestGroupFns.remove, + [TYPE_REQUEST_GROUP]: requestGroup => ({type: ENTITY_UPDATE, requestGroup}), [TYPE_RESPONSE]: response => ({type: ENTITY_UPDATE, response}), [TYPE_REQUEST]: request => ({type: ENTITY_REMOVE, request}) }; diff --git a/app/redux/modules/requestGroups.js b/app/redux/modules/requestGroups.js index aae168e926..661c8fdf3f 100644 --- a/app/redux/modules/requestGroups.js +++ b/app/redux/modules/requestGroups.js @@ -3,8 +3,6 @@ import {combineReducers} from 'redux' import {show} from './modals' import {MODAL_ENVIRONMENT_EDITOR, MODAL_REQUEST_GROUP_RENAME} from '../../lib/constants' -export const REQUEST_GROUP_UPDATE = 'request-groups/update'; -export const REQUEST_GROUP_DELETE = 'request-groups/delete'; export const REQUEST_GROUP_TOGGLE = 'request-groups/toggle'; // ~~~~~~~~ // @@ -13,24 +11,12 @@ export const REQUEST_GROUP_TOGGLE = 'request-groups/toggle'; function allReducer (state = [], action) { switch (action.type) { - - case REQUEST_GROUP_UPDATE: - const i = state.findIndex(rg => rg._id === action.requestGroup._id); - - if (i === -1) { - return [action.requestGroup, ...state]; - } else { - return [...state.slice(0, i), action.requestGroup, ...state.slice(i + 1)] - } case REQUEST_GROUP_TOGGLE: return state.map( rg => rg._id === action._id ? Object.assign({}, rg, {collapsed: !rg.collapsed}) : rg ); - case REQUEST_GROUP_DELETE: - return state.filter(rg => rg._id !== action.requestGroup._id); - default: return state; } @@ -45,14 +31,6 @@ export default combineReducers({ // ACTIONS // // ~~~~~~~ // -export function update (requestGroup) { - return {type: REQUEST_GROUP_UPDATE, requestGroup}; -} - -export function remove (requestGroup) { - return {type: REQUEST_GROUP_DELETE, requestGroup}; -} - export function toggle (requestGroup) { return {type: REQUEST_GROUP_TOGGLE, requestGroup} } From b44779d3909711b776740d4171faa9e680935e3d Mon Sep 17 00:00:00 2001 From: Gregory Schier Date: Mon, 25 Apr 2016 09:50:28 -0700 Subject: [PATCH 10/13] Some more tweaks --- app/containers/App.js | 5 ++++ app/containers/RequestActionsDropdown.js | 2 +- app/containers/WorkspaceDropdown.js | 22 +++++++++------- app/database/index.js | 2 +- app/redux/modules/entities.js | 4 +-- app/redux/modules/workspaces.js | 33 ------------------------ 6 files changed, 21 insertions(+), 47 deletions(-) diff --git a/app/containers/App.js b/app/containers/App.js index af65a725c3..ce4177c205 100644 --- a/app/containers/App.js +++ b/app/containers/App.js @@ -116,6 +116,11 @@ App.propTypes = { hide: PropTypes.func.isRequired }) }).isRequired, + entities: PropTypes.shape({ + requests: PropTypes.object.isRequired, + requestGroups: PropTypes.object.isRequired, + responses: PropTypes.object.isRequired + }).isRequired, workspaces: PropTypes.shape({ active: PropTypes.object }).isRequired, diff --git a/app/containers/RequestActionsDropdown.js b/app/containers/RequestActionsDropdown.js index 6b9a48b5d6..d5bba2c807 100644 --- a/app/containers/RequestActionsDropdown.js +++ b/app/containers/RequestActionsDropdown.js @@ -17,7 +17,7 @@ class RequestActionsDropdown extends Component {
    • diff --git a/app/containers/WorkspaceDropdown.js b/app/containers/WorkspaceDropdown.js index cb47a9389c..ca2267c6be 100644 --- a/app/containers/WorkspaceDropdown.js +++ b/app/containers/WorkspaceDropdown.js @@ -8,7 +8,6 @@ import {connect} from 'react-redux' import Dropdown from '../components/base/Dropdown' import DropdownDivider from '../components/base/DropdownDivider' import * as RequestGroupActions from '../redux/modules/requestGroups' -import * as WorkspaceActions from '../redux/modules/workspaces' import * as db from '../database' import importData from '../lib/import' @@ -31,16 +30,16 @@ class WorkspaceDropdown extends Component { } render () { - const {actions, loading, workspaces, ...other} = this.props; + const {actions, loading, workspaces, entities, ...other} = this.props; - const workspace = workspaces.active; + const allWorkspaces = Object.keys(entities.workspaces).map(id => entities.workspaces[id]); return (
    • -
    • @@ -73,14 +72,14 @@ class WorkspaceDropdown extends Component {
    • -
    • - {workspaces.all.map(w => { + {allWorkspaces.map(w => { return w._id === workspaces.active._id ? null : (
    • -
    • @@ -72,24 +92,24 @@ class WorkspaceDropdown extends Component {
    • -
    • {allWorkspaces.map(w => { - return w._id === workspaces.active._id ? null : ( + return w._id === workspace._id ? null : (
    • -
    • ) })}
    • -
    • @@ -107,7 +127,7 @@ class WorkspaceDropdown extends Component { WorkspaceDropdown.propTypes = { loading: PropTypes.bool.isRequired, workspaces: PropTypes.shape({ - active: PropTypes.object.isRequired + activeId: PropTypes.object }), entities: PropTypes.shape({ workspaces: PropTypes.object.isRequired @@ -115,6 +135,9 @@ WorkspaceDropdown.propTypes = { actions: PropTypes.shape({ requestGroups: PropTypes.shape({ showEnvironmentEditModal: PropTypes.func.isRequired + }), + workspaces: PropTypes.shape({ + activate: PropTypes.func.isRequired }) }) }; @@ -131,7 +154,8 @@ function mapStateToProps (state) { function mapDispatchToProps (dispatch) { return { actions: { - requestGroups: bindActionCreators(RequestGroupActions, dispatch) + requestGroups: bindActionCreators(RequestGroupActions, dispatch), + workspaces: bindActionCreators(WorkspaceActions, dispatch) } } } diff --git a/app/database/index.js b/app/database/index.js index e1a64eb952..fc2e4b8e16 100644 --- a/app/database/index.js +++ b/app/database/index.js @@ -185,15 +185,10 @@ export function workspaceCreate (patch = {}) { return modelCreate(TYPE_WORKSPACE, 'wrk', { name: 'New Workspace', activeRequestId: null, - activated: Date.now(), // TODO: Delete this property (replace with something better) environments: [] }, patch); } -export function workspaceActivate (workspace) { - return update(workspace, {activated: Date.now()}); -} - export function workspaceAll () { return db.find({ selector: {type: 'Workspace'} @@ -211,18 +206,6 @@ export function workspaceAll () { }) } -export function workspaceGetActive () { - return db.find({ - selector: { - activated: {$gte: 0}, // HACK: because can't use $exists here? - type: {$eq: 'Workspace'} - }, - sort: [{activated: 'desc'}], - limit: 1 - }) -} - - // ~~~~~~~~ // // SETTINGS // // ~~~~~~~~ // diff --git a/app/redux/modules/entities.js b/app/redux/modules/entities.js index d95ed1ac53..de5b5e8303 100644 --- a/app/redux/modules/entities.js +++ b/app/redux/modules/entities.js @@ -10,33 +10,14 @@ const ENTITY_REMOVE = 'entities/remove'; // REDUCERS // // ~~~~~~~~ // -function generateEntityReducer (referenceName, updateAction, deleteAction) { - return function (state = {}, action) { - switch (action.type) { - - case updateAction: - const doc = action[referenceName]; - return {...state, [doc._id]: doc}; - - case deleteAction: - const newState = Object.assign({}, state); - delete newState[action[referenceName]._id]; - return newState; - - default: - return state; - } - } -} - function genericEntityReducer (referenceName) { return function (state = {}, action) { const doc = action[referenceName]; - + if (!doc) { return state; } - + switch (action.type) { case ENTITY_UPDATE: @@ -53,14 +34,8 @@ function genericEntityReducer (referenceName) { } } -const workspaces = generateEntityReducer( - 'workspace', - workspaceFns.WORKSPACE_UPDATE, - 'dne' -); - export default combineReducers({ - workspaces, + workspaces: genericEntityReducer('workspace'), requestGroups: genericEntityReducer('requestGroup'), requests: genericEntityReducer('request'), responses: genericEntityReducer('response') @@ -72,15 +47,15 @@ export default combineReducers({ // ~~~~~~~ // const updateFns = { - [TYPE_WORKSPACE]: workspaceFns.update, + [TYPE_WORKSPACE]: workspace => ({type: ENTITY_UPDATE, workspace}), [TYPE_REQUEST_GROUP]: requestGroup => ({type: ENTITY_UPDATE, requestGroup}), [TYPE_RESPONSE]: response => ({type: ENTITY_UPDATE, response}), [TYPE_REQUEST]: request => ({type: ENTITY_UPDATE, request}) }; const removeFns = { - [TYPE_WORKSPACE]: workspace => ({type: ENTITY_UPDATE, workspace}), - [TYPE_REQUEST_GROUP]: requestGroup => ({type: ENTITY_UPDATE, requestGroup}), + [TYPE_WORKSPACE]: workspace => ({type: ENTITY_REMOVE, workspace}), + [TYPE_REQUEST_GROUP]: requestGroup => ({type: ENTITY_REMOVE, requestGroup}), [TYPE_RESPONSE]: response => ({type: ENTITY_UPDATE, response}), [TYPE_REQUEST]: request => ({type: ENTITY_REMOVE, request}) }; diff --git a/app/redux/modules/workspaces.js b/app/redux/modules/workspaces.js index fa149a5800..fe7104ad50 100644 --- a/app/redux/modules/workspaces.js +++ b/app/redux/modules/workspaces.js @@ -1,6 +1,6 @@ import {combineReducers} from 'redux' -export const WORKSPACE_UPDATE = 'workspaces/update'; +export const WORKSPACE_ACTIVATE = 'workspaces/activate'; // ~~~~~~~~ // // REDUCERS // @@ -8,15 +8,9 @@ export const WORKSPACE_UPDATE = 'workspaces/update'; function activeReducer (state = null, action) { switch (action.type) { - - case WORKSPACE_UPDATE: - if (state && state._id === action.workspace._id) { - return action.workspace; - } else if (state) { - return action.workspace.activated > state.activated ? action.workspace : state; - } else { - return action.workspace; - } + + case WORKSPACE_ACTIVATE: + return action.workspace._id; default: return state; @@ -24,7 +18,7 @@ function activeReducer (state = null, action) { } export default combineReducers({ - active: activeReducer + activeId: activeReducer }); @@ -32,6 +26,6 @@ export default combineReducers({ // ACTIONS // // ~~~~~~~ // -export function update (workspace) { - return {type: WORKSPACE_UPDATE, workspace}; +export function activate (workspace) { + return {type: WORKSPACE_ACTIVATE, workspace}; } From a2f4cfd8bf2fcb4f95c92e00dc3a44aff9d7b58c Mon Sep 17 00:00:00 2001 From: Gregory Schier Date: Mon, 25 Apr 2016 16:13:07 -0700 Subject: [PATCH 12/13] Re-implemented filtering in the Sidbare --- app/components/RequestPane.js | 2 +- app/components/RequestUrlBar.js | 4 +- app/components/ResponsePane.js | 27 ++--- app/components/Sidebar.js | 148 +++++++++-------------- app/components/SidebarRequestGroupRow.js | 71 +++++++++++ app/css/components/dropdown.scss | 2 +- app/css/components/tabs.scss | 27 +++-- app/css/constants/dimensions.scss | 4 + 8 files changed, 168 insertions(+), 117 deletions(-) create mode 100644 app/components/SidebarRequestGroupRow.js diff --git a/app/components/RequestPane.js b/app/components/RequestPane.js index f5350d3993..44917e2da3 100644 --- a/app/components/RequestPane.js +++ b/app/components/RequestPane.js @@ -48,7 +48,7 @@ class RequestPane extends Component { - +
      • diff --git a/app/components/RequestUrlBar.js b/app/components/RequestUrlBar.js index 44aa46d8b9..fb07b1e4d3 100644 --- a/app/components/RequestUrlBar.js +++ b/app/components/RequestUrlBar.js @@ -21,7 +21,9 @@ class UrlInput extends Component {
          {METHODS.map(m => (
        • - +
        • ))}
        diff --git a/app/components/ResponsePane.js b/app/components/ResponsePane.js index 5011b9f895..b89e0ac9f0 100644 --- a/app/components/ResponsePane.js +++ b/app/components/ResponsePane.js @@ -1,8 +1,9 @@ import React, {Component, PropTypes} from 'react' import {Tab, Tabs, TabList, TabPanel} from 'react-tabs' -import StatusTag from '../components/StatusTag' +import Dropdown from '../components/base/Dropdown' import Editor from '../components/base/Editor' +import StatusTag from '../components/StatusTag' import SizeTag from '../components/SizeTag' import TimeTag from '../components/TimeTag' @@ -39,8 +40,17 @@ class ResponsePane extends Component {
    - - + + + + +
      +
    • +
    • +
    • +
    +
    +
    @@ -54,17 +64,6 @@ class ResponsePane extends Component { }} /> - - -
    diff --git a/app/components/Sidebar.js b/app/components/Sidebar.js index 09ebd0ce77..331f9820ff 100644 --- a/app/components/Sidebar.js +++ b/app/components/Sidebar.js @@ -1,8 +1,7 @@ import React, {Component, PropTypes} from 'react' -import classnames from 'classnames' import WorkspaceDropdown from './../containers/WorkspaceDropdown' -import RequestGroupActionsDropdown from './../containers/RequestGroupActionsDropdown' import DebouncingInput from './base/DebouncingInput' +import SidebarRequestGroupRow from './SidebarRequestGroupRow' import SidebarRequestRow from './SidebarRequestRow' class Sidebar extends Component { @@ -10,105 +9,70 @@ class Sidebar extends Component { this.props.changeFilter(value); } - renderRequestGroupRow (child, parent) { - const { - filter, - activeRequestId, - addRequestToRequestGroup, - toggleRequestGroup - } = this.props; - - const requestGroup = child.doc.type === 'RequestGroup' ? child.doc : null; + _filterChildren (filter, children, extra = null) { + return children.filter(child => { + if (child.doc.type !== 'Request') { + return true; + } - if (!requestGroup) { - return child.children.map(c => this._renderChild(c, child)); - } + const request = child.doc; - // Don't show folder if it was not in the filter - if (filter && !child.children.length) { - return null; - } - - const isActive = activeRequestId && child.children.find(c => c.doc._id == activeRequestId); - - let folderIconClass = 'fa-folder'; - let expanded = !requestGroup.collapsed; - folderIconClass += !expanded ? '' : '-open'; - folderIconClass += isActive ? '' : '-o'; - - const sidebarItemClassNames = classnames( - 'sidebar__item', - 'sidebar__item--bordered', - {'sidebar__item--active': isActive} - ); - - child.children.sort((a, b) => a.doc._id > b.doc._id ? -1 : 1); - - return ( -
  • -
    -
    - -
    -
    - - -
    -
    -
      - {expanded && !child.children.length ? this.renderRequestRow() : null} - {!expanded ? null : child.children.map(c => this._renderChild(c, child))} -
    -
  • - ); - } - - renderRequestRow (child = null, parent = null) { - const request = child ? child.doc : null; - const requestGroup = parent ? parent.doc : null; - const {activeRequestId, activateRequest} = this.props; - const isActive = request && activeRequestId && request._id === activeRequestId || false; - - return ( - - ) - } - - _renderChild (child, parent = null) { - const {filter} = this.props; - - if (child.doc.type === 'Request') { - const r = child.doc; - const toMatch = `${r.method}❅${r.name}`.toLowerCase(); + const otherMatches = extra || ''; + const toMatch = `${request.method}❅${request.name}❅${otherMatches}`.toLowerCase(); const matchTokens = filter.toLowerCase().split(' '); + for (let i = 0; i < matchTokens.length; i++) { let token = `${matchTokens[i]}`; if (toMatch.indexOf(token) === -1) { // Filter failed. Don't render children - return null; + return false; } } - return this.renderRequestRow(child, parent) - } else if (child.doc.type === 'RequestGroup') { - return this.renderRequestGroupRow(child, parent); - } else { - console.error('Unknown child type', child.doc.type); - } + return true; + }) + } + + _renderChildren (children, requestGroup) { + const {filter} = this.props; + + const filteredChildren = this._filterChildren( + filter, + children, + requestGroup && requestGroup.name + ).sort((a, b) => a.doc._id > b.doc._id ? -1 : 1); + + return filteredChildren.map(child => { + if (child.doc.type === 'Request') { + return ( + + ) + } else if (child.doc.type === 'RequestGroup') { + const requestGroup = child.doc; + const isActive = !!child.children.find(c => c.doc._id === this.props.activeRequestId); + + return ( + + {this._renderChildren(child.children, requestGroup)} + + ) + } else { + console.error('Unknown child type', child.doc.type); + return null; + } + }) } render () { @@ -122,7 +86,7 @@ class Sidebar extends Component {
      - {children.map(c => this._renderChild(c))} + {this._renderChildren(children)}
    diff --git a/app/components/SidebarRequestGroupRow.js b/app/components/SidebarRequestGroupRow.js new file mode 100644 index 0000000000..38b5c2cc0a --- /dev/null +++ b/app/components/SidebarRequestGroupRow.js @@ -0,0 +1,71 @@ +import React, {Component, PropTypes} from 'react' +import classnames from 'classnames' +import RequestGroupActionsDropdown from './../containers/RequestGroupActionsDropdown' + +class SidebarRequestGroupRow extends Component { + render () { + const { + children, + numChildren, + requestGroup, + isActive, + toggleRequestGroup, + addRequestToRequestGroup + } = this.props; + + // If we are supposed to have children, but aren't passed any, we are probably + // filtering so don't render anything + if (numChildren >= 0 && children.length === 0) { + return null; + } + + let folderIconClass = 'fa-folder'; + let expanded = !requestGroup.collapsed; + folderIconClass += !expanded ? '' : '-open'; + folderIconClass += isActive ? '' : '-o'; + + const sidebarItemClassNames = classnames( + 'sidebar__item', + 'sidebar__item--bordered', + {'sidebar__item--active': isActive} + ); + + return ( +
  • +
    +
    + +
    +
    + + +
    +
    +
      + {expanded ? children : null} +
    +
  • + ); + } +} + +SidebarRequestGroupRow.propTypes = { + // Functions + toggleRequestGroup: PropTypes.func.isRequired, + addRequestToRequestGroup: PropTypes.func.isRequired, + + // Other + isActive: PropTypes.bool.isRequired, + numChildren: PropTypes.number.isRequired, + requestGroup: PropTypes.object.isRequired +}; + +export default SidebarRequestGroupRow; diff --git a/app/css/components/dropdown.scss b/app/css/components/dropdown.scss index 1cdc6c2eb5..3de31edd07 100644 --- a/app/css/components/dropdown.scss +++ b/app/css/components/dropdown.scss @@ -28,7 +28,7 @@ padding: $padding-sm $padding-md $padding-sm $padding-sm; width: 100%; display: block; - color: $font-super-light-bg; + color: $font-super-light-bg !important; &:hover { background: $hl-sm; diff --git a/app/css/components/tabs.scss b/app/css/components/tabs.scss index 7480531738..c3dfdc5d64 100644 --- a/app/css/components/tabs.scss +++ b/app/css/components/tabs.scss @@ -9,6 +9,7 @@ $border-color: $hl-md; align-items: flex-start; align-content: flex-start; height: $line-height-sm; + line-height: $line-height-sm; &::after { width: 100%; @@ -21,12 +22,16 @@ $border-color: $hl-md; .ReactTabs__Tab { align-self: flex-start; - padding: $padding-sm / 4 $padding-md; height: $line-height-sm; box-sizing: border-box; + border: 1px solid transparent; border-bottom: 1px solid $border-color; border-top: 0 !important; + * { + color: $hl-xxl; + } + &:first-child { border-left-color: transparent; } @@ -35,23 +40,29 @@ $border-color: $hl-md; outline: 0; } - & > button { - color: $hl-xxl; - position: relative; + & > * { height: 100%; width: 100%; - border-left: 1px solid transparent; - border-right: 1px solid transparent; + padding-left: $padding-md / 3; + padding-right: $padding-md / 3; + + &:first-child { + padding-left: $padding-md; + } + + &:last-child { + padding-right: $padding-md; + } } + } .ReactTabs__Tab--selected { border: 1px solid $border-color; border-bottom-color: transparent; - & > button { + * { color: inherit; - border: 0 !important; } & > button:hover { diff --git a/app/css/constants/dimensions.scss b/app/css/constants/dimensions.scss index bf01b3f3ea..b0ae578f43 100644 --- a/app/css/constants/dimensions.scss +++ b/app/css/constants/dimensions.scss @@ -37,6 +37,10 @@ $modal-width: 50rem; $breakpoint-md: 790px; $breakpoint-sm: 580px; +.txt-xs { + font-size: $font-size-xs; +} + .txt-sm { font-size: $font-size-sm; } From 33d3ada06529184c764764cc5e1c69a976fbb025 Mon Sep 17 00:00:00 2001 From: Gregory Schier Date: Mon, 25 Apr 2016 21:17:41 -0700 Subject: [PATCH 13/13] Switch to get the newest response --- app/components/RequestPane.js | 6 +++--- app/components/ResponsePane.js | 2 +- app/components/Sidebar.js | 1 + app/components/SidebarRequestGroupRow.js | 14 +++++++++++--- app/containers/App.js | 4 +++- app/containers/Prompts.js | 14 +++++++++++++- app/containers/WorkspaceDropdown.js | 16 +++++++++++----- app/css/components/tabs.scss | 4 ++-- app/database/util.js | 2 +- app/lib/constants.js | 3 ++- app/redux/modules/requestGroups.js | 18 +----------------- app/redux/modules/workspaces.js | 7 +++++++ 12 files changed, 56 insertions(+), 35 deletions(-) diff --git a/app/components/RequestPane.js b/app/components/RequestPane.js index 44917e2da3..444c70410b 100644 --- a/app/components/RequestPane.js +++ b/app/components/RequestPane.js @@ -50,10 +50,10 @@ class RequestPane extends Component {
      -
    • -
    • + {/*
    • */}
    • -
    • +
    • +
    diff --git a/app/components/ResponsePane.js b/app/components/ResponsePane.js index b89e0ac9f0..87cf42a31b 100644 --- a/app/components/ResponsePane.js +++ b/app/components/ResponsePane.js @@ -46,7 +46,7 @@ class ResponsePane extends Component {
    • -
    • +
    diff --git a/app/components/Sidebar.js b/app/components/Sidebar.js index 331f9820ff..4190c2afe1 100644 --- a/app/components/Sidebar.js +++ b/app/components/Sidebar.js @@ -60,6 +60,7 @@ class Sidebar extends Component { = 0 && children.length === 0) { + if (hideIfNoChildren && children.length === 0) { return null; } @@ -50,6 +51,13 @@ class SidebarRequestGroupRow extends Component {
      + {!expanded || children.length > 0 ? null : ( + {}} + isActive={false} + request={null} + /> + )} {expanded ? children : null}
    @@ -64,7 +72,7 @@ SidebarRequestGroupRow.propTypes = { // Other isActive: PropTypes.bool.isRequired, - numChildren: PropTypes.number.isRequired, + hideIfNoChildren: PropTypes.number.isRequired, requestGroup: PropTypes.object.isRequired }; diff --git a/app/containers/App.js b/app/containers/App.js index 2c4e104084..481ff275f3 100644 --- a/app/containers/App.js +++ b/app/containers/App.js @@ -53,7 +53,9 @@ class App extends Component { const allRequests = Object.keys(entities.requests).map(id => entities.requests[id]); const allRequestGroups = Object.keys(entities.requestGroups).map(id => entities.requestGroups[id]); - const activeResponse = responses.find(r => r.parentId === activeRequestId); + const activeResponse = responses.sort( + (a, b) => a._id > b._id ? -1 : 1 + ).find(r => r.parentId === activeRequestId); const children = this._generateSidebarTree( workspace._id, diff --git a/app/containers/Prompts.js b/app/containers/Prompts.js index 8aadf32b93..83ad938e23 100644 --- a/app/containers/Prompts.js +++ b/app/containers/Prompts.js @@ -6,7 +6,11 @@ import * as ModalActions from '../redux/modules/modals' import PromptModal from '../components/base/PromptModal' import * as db from '../database' -import {MODAL_REQUEST_RENAME, MODAL_REQUEST_GROUP_RENAME} from '../lib/constants'; +import { + MODAL_REQUEST_RENAME, + MODAL_REQUEST_GROUP_RENAME, + MODAL_WORKSPACE_RENAME +} from '../lib/constants'; class Prompts extends Component { constructor (props) { @@ -27,6 +31,14 @@ class Prompts extends Component { db.update(modal.data.requestGroup, {name}) } }; + + this._prompts[MODAL_WORKSPACE_RENAME] = { + header: 'Rename Workspace', + submit: 'Rename', + onSubmit: (modal, name) => { + db.update(modal.data.workspace, {name}) + } + }; } render () { diff --git a/app/containers/WorkspaceDropdown.js b/app/containers/WorkspaceDropdown.js index 917b2a3b29..ec76236d82 100644 --- a/app/containers/WorkspaceDropdown.js +++ b/app/containers/WorkspaceDropdown.js @@ -77,20 +77,25 @@ class WorkspaceDropdown extends Component {
  • -
  • -
  • + {/*
  • -
  • + */}
  • +
  • + +