Started on new Sidebar tree stuff

This commit is contained in:
Gregory Schier 2016-04-24 22:50:42 -07:00
parent 775a623c80
commit 410a218573
8 changed files with 127 additions and 201 deletions

View File

@ -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 (
<li key={requestGroup._id}>
<div className={sidebarItemClassNames}>
@ -88,20 +64,22 @@ class Sidebar extends Component {
</div>
</div>
<ul>
{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))}
</ul>
</li>
);
}
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 (
<SidebarRequestRow
key={request._id}
key={request ? request._id : null}
activateRequest={activateRequest}
isActive={isActive}
request={request}
@ -110,9 +88,32 @@ class Sidebar extends Component {
)
}
_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 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 this.renderRequestRow(child, parent)
} else if (child.doc.type === 'RequestGroup') {
return this.renderRequestGroupRow(child, parent);
} else {
console.error('Unknown child type', child.doc.type);
}
}
render () {
const {filter, requestGroups} = this.props;
const {filter, children} = this.props;
return (
<section className="sidebar bg-dark grid--v section section--bordered">
<header className="header bg-brand section__header">
@ -121,8 +122,7 @@ class Sidebar extends Component {
<div className="grid--v grid--start grid__cell section__body">
<ul
className="grid--v grid--start grid__cell sidebar__scroll hover-scrollbars sidebar__request-list">
{this.renderRequestGroupRow(null)}
{requestGroups.map(requestGroup => this.renderRequestGroupRow(requestGroup))}
{children.map(c => this._renderChild(c))}
</ul>
<div className="grid grid--center">
<div className="grid__cell form-control form-control--underlined">
@ -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

View File

@ -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;

View File

@ -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 (
<div className="grid bg-super-dark tall">
<Sidebar
workspaceId={workspaces.active._id}
activateRequest={db.requestActivate}
activateRequest={r => 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}
/>
<div className="grid wide grid--collapse">
<RequestPane
@ -96,13 +119,8 @@ App.propTypes = {
workspaces: PropTypes.shape({
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
filter: PropTypes.string.isRequired
}).isRequired,
modals: PropTypes.array.isRequired
};
@ -111,9 +129,8 @@ function mapStateToProps (state) {
return {
actions: state.actions,
workspaces: state.workspaces,
requestGroups: state.requestGroups,
requests: state.requests,
responses: state.responses,
entities: state.entities,
modals: state.modals
};
}

View File

@ -3,8 +3,6 @@ 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 PromptModal from '../components/base/PromptModal'
import * as db from '../database'
@ -65,12 +63,6 @@ Prompts.propTypes = {
actions: PropTypes.shape({
modals: PropTypes.shape({
hide: PropTypes.func.isRequired
}),
requestGroups: PropTypes.shape({
update: PropTypes.func.isRequired
}),
requests: PropTypes.shape({
update: PropTypes.func.isRequired
})
}),
modals: PropTypes.array.isRequired
@ -86,9 +78,7 @@ function mapStateToProps (state) {
function mapDispatchToProps (dispatch) {
return {
actions: {
requests: bindActionCreators(RequestActions, dispatch),
modals: bindActionCreators(ModalActions, dispatch),
requestGroups: bindActionCreators(RequestGroupActions, dispatch)
modals: bindActionCreators(ModalActions, dispatch)
}
}
}

View File

@ -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 = {};
@ -145,10 +145,6 @@ export function requestCopy (request) {
return requestCreate(Object.assign({}, request, {name}));
}
export function requestActivate (request) {
return update(request, {activated: Date.now()});
}
// ~~~~~~~~~~~~~ //
// REQUEST GROUP //

View File

@ -3,9 +3,9 @@ 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'
const ENTITY_UPDATE = 'entities/update';
const ENTITY_REMOVE = 'entities/remove';
// ~~~~~~~~ //
// REDUCERS //
@ -30,6 +30,30 @@ function generateEntityReducer (referenceName, updateAction, deleteAction) {
}
}
function genericEntityReducer (referenceName) {
return function (state = {}, action) {
const doc = action[referenceName];
if (!doc) {
return state;
}
switch (action.type) {
case ENTITY_UPDATE:
return {...state, [doc._id]: doc};
case ENTITY_REMOVE:
const newState = Object.assign({}, state);
delete newState[action[referenceName]._id];
return newState;
default:
return state;
}
}
}
const workspaces = generateEntityReducer(
'workspace',
workspaceFns.WORKSPACE_UPDATE,
@ -37,28 +61,16 @@ const workspaces = generateEntityReducer(
);
const requestGroups = generateEntityReducer(
'requestGroup',
'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
requests: genericEntityReducer('request'),
responses: genericEntityReducer('response')
})
@ -69,33 +81,21 @@ export default combineReducers({
const updateFns = {
[TYPE_WORKSPACE]: workspaceFns.update,
[TYPE_REQUEST_GROUP]: requestGroupFns.update,
[TYPE_REQUEST]: requestFns.update,
[TYPE_RESPONSE]: responseFns.update
[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]: 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);
}

View File

@ -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};
}

View File

@ -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...