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); this.props.changeFilter(value);
} }
renderRequestGroupRow (requestGroup = null) { renderRequestGroupRow (child, parent) {
const { const {
filter, filter,
activeRequestId, activeRequestId,
addRequestToRequestGroup, addRequestToRequestGroup,
toggleRequestGroup, toggleRequestGroup
requests,
workspaceId
} = this.props; } = this.props;
let filteredRequests = requests.filter( const requestGroup = child.doc.type === 'RequestGroup' ? child.doc : null;
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;
}
);
if (!requestGroup) { if (!requestGroup) {
filteredRequests = filteredRequests.filter(r => r.parentId === workspaceId); return child.children.map(c => this._renderChild(c, child));
return filteredRequests.map(request => this.renderRequestRow(request));
} }
// 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 // Don't show folder if it was not in the filter
if (filter && !filteredRequests.length) { if (filter && !child.children.length) {
return null; 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 folderIconClass = 'fa-folder';
let expanded = !requestGroup.collapsed; let expanded = !requestGroup.collapsed;
@ -68,6 +42,8 @@ class Sidebar extends Component {
{'sidebar__item--active': isActive} {'sidebar__item--active': isActive}
); );
child.children.sort((a, b) => a.doc._id > b.doc._id ? -1 : 1);
return ( return (
<li key={requestGroup._id}> <li key={requestGroup._id}>
<div className={sidebarItemClassNames}> <div className={sidebarItemClassNames}>
@ -88,20 +64,22 @@ class Sidebar extends Component {
</div> </div>
</div> </div>
<ul> <ul>
{expanded && !filteredRequests.length ? this.renderRequestRow() : null} {expanded && !child.children.length ? this.renderRequestRow() : null}
{!expanded ? null : filteredRequests.map(request => this.renderRequestRow(request, requestGroup))} {!expanded ? null : child.children.map(c => this._renderChild(c, child))}
</ul> </ul>
</li> </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 {activeRequestId, activateRequest} = this.props;
const isActive = request && activeRequestId && request._id === activeRequestId; const isActive = request && activeRequestId && request._id === activeRequestId || false;
return ( return (
<SidebarRequestRow <SidebarRequestRow
key={request._id} key={request ? request._id : null}
activateRequest={activateRequest} activateRequest={activateRequest}
isActive={isActive} isActive={isActive}
request={request} 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 () { render () {
const {filter, requestGroups} = this.props; const {filter, children} = this.props;
return ( return (
<section className="sidebar bg-dark grid--v section section--bordered"> <section className="sidebar bg-dark grid--v section section--bordered">
<header className="header bg-brand section__header"> <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"> <div className="grid--v grid--start grid__cell section__body">
<ul <ul
className="grid--v grid--start grid__cell sidebar__scroll hover-scrollbars sidebar__request-list"> className="grid--v grid--start grid__cell sidebar__scroll hover-scrollbars sidebar__request-list">
{this.renderRequestGroupRow(null)} {children.map(c => this._renderChild(c))}
{requestGroups.map(requestGroup => this.renderRequestGroupRow(requestGroup))}
</ul> </ul>
<div className="grid grid--center"> <div className="grid grid--center">
<div className="grid__cell form-control form-control--underlined"> <div className="grid__cell form-control form-control--underlined">
@ -146,12 +146,11 @@ Sidebar.propTypes = {
toggleRequestGroup: PropTypes.func.isRequired, toggleRequestGroup: PropTypes.func.isRequired,
addRequestToRequestGroup: PropTypes.func.isRequired, addRequestToRequestGroup: PropTypes.func.isRequired,
changeFilter: PropTypes.func.isRequired, changeFilter: PropTypes.func.isRequired,
// Other // Other
requests: PropTypes.array.isRequired, children: PropTypes.array.isRequired,
requestGroups: PropTypes.array.isRequired,
workspaceId: PropTypes.string.isRequired, workspaceId: PropTypes.string.isRequired,
// Optional // Optional
filter: PropTypes.string, filter: PropTypes.string,
activeRequestId: PropTypes.string activeRequestId: PropTypes.string

View File

@ -37,11 +37,11 @@ SidebarRequestRow.propTypes = {
activateRequest: PropTypes.func.isRequired, activateRequest: PropTypes.func.isRequired,
// Other // Other
request: PropTypes.object.isRequired,
isActive: PropTypes.bool.isRequired, isActive: PropTypes.bool.isRequired,
// Optional // Optional
requestGroup: PropTypes.object requestGroup: PropTypes.object,
request: PropTypes.object
}; };
export default SidebarRequestRow; export default SidebarRequestRow;

View File

@ -24,24 +24,47 @@ class App extends Component {
} }
} }
render () { _generateSidebarTree (parentId, entities) {
const {actions, modals, workspaces, requestGroups, requests, responses} = this.props; 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; render () {
const activeResponse = activeRequest ? responses[activeRequest._id] : undefined; 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 ( return (
<div className="grid bg-super-dark tall"> <div className="grid bg-super-dark tall">
<Sidebar <Sidebar
workspaceId={workspaces.active._id} workspaceId={workspaces.active._id}
activateRequest={db.requestActivate} activateRequest={r => db.update(workspaces.active, {activeRequestId: r._id})}
changeFilter={actions.requests.changeFilter} changeFilter={actions.requests.changeFilter}
addRequestToRequestGroup={requestGroup => db.requestCreate({parentId: requestGroup._id})} addRequestToRequestGroup={requestGroup => db.requestCreate({parentId: requestGroup._id})}
toggleRequestGroup={requestGroup => db.update(requestGroup, {collapsed: !requestGroup.collapsed})} toggleRequestGroup={requestGroup => db.update(requestGroup, {collapsed: !requestGroup.collapsed})}
activeRequestId={activeRequest._id} activeRequestId={activeRequest ? activeRequest._id : null}
filter={requests.filter} filter={requests.filter}
requestGroups={requestGroups.all.sort((a, b) => a._id > b._id ? -1 : 1)} children={children}
requests={requests.all.sort((a, b) => a._id > b._id ? -1 : 1)}
/> />
<div className="grid wide grid--collapse"> <div className="grid wide grid--collapse">
<RequestPane <RequestPane
@ -96,13 +119,8 @@ App.propTypes = {
workspaces: PropTypes.shape({ workspaces: PropTypes.shape({
active: PropTypes.object active: PropTypes.object
}).isRequired, }).isRequired,
responses: PropTypes.object.isRequired,
requestGroups: PropTypes.shape({
all: PropTypes.array.isRequired
}).isRequired,
requests: PropTypes.shape({ requests: PropTypes.shape({
all: PropTypes.array.isRequired, filter: PropTypes.string.isRequired
active: PropTypes.object
}).isRequired, }).isRequired,
modals: PropTypes.array.isRequired modals: PropTypes.array.isRequired
}; };
@ -111,9 +129,8 @@ function mapStateToProps (state) {
return { return {
actions: state.actions, actions: state.actions,
workspaces: state.workspaces, workspaces: state.workspaces,
requestGroups: state.requestGroups,
requests: state.requests, requests: state.requests,
responses: state.responses, entities: state.entities,
modals: state.modals modals: state.modals
}; };
} }

View File

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

View File

@ -13,7 +13,7 @@ export const TYPE_RESPONSE = 'Response';
let db = new PouchDB('insomnia.db', {adapter: 'websql'}); let db = new PouchDB('insomnia.db', {adapter: 'websql'});
// For browser console debugging // For browser console debugging
// global.db = db; global.db = db;
let changeListeners = {}; let changeListeners = {};
@ -145,10 +145,6 @@ export function requestCopy (request) {
return requestCreate(Object.assign({}, request, {name})); return requestCreate(Object.assign({}, request, {name}));
} }
export function requestActivate (request) {
return update(request, {activated: Date.now()});
}
// ~~~~~~~~~~~~~ // // ~~~~~~~~~~~~~ //
// REQUEST GROUP // // 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 {TYPE_WORKSPACE, TYPE_REQUEST_GROUP, TYPE_REQUEST, TYPE_RESPONSE} from '../../database/index'
import * as workspaceFns from './workspaces' import * as workspaceFns from './workspaces'
import * as requestGroupFns from './requestGroups' 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 // // 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( const workspaces = generateEntityReducer(
'workspace', 'workspace',
workspaceFns.WORKSPACE_UPDATE, workspaceFns.WORKSPACE_UPDATE,
@ -37,28 +61,16 @@ const workspaces = generateEntityReducer(
); );
const requestGroups = generateEntityReducer( const requestGroups = generateEntityReducer(
'requestGroup', 'requestGroup',
requestGroupFns.REQUEST_GROUP_UPDATE, requestGroupFns.REQUEST_GROUP_UPDATE,
requestGroupFns.REQUEST_GROUP_DELETE 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({ export default combineReducers({
workspaces, workspaces,
requestGroups, requestGroups,
requests, requests: genericEntityReducer('request'),
responses responses: genericEntityReducer('response')
}) })
@ -69,33 +81,21 @@ export default combineReducers({
const updateFns = { const updateFns = {
[TYPE_WORKSPACE]: workspaceFns.update, [TYPE_WORKSPACE]: workspaceFns.update,
[TYPE_REQUEST_GROUP]: requestGroupFns.update, [TYPE_REQUEST_GROUP]: requestGroupFns.update,
[TYPE_REQUEST]: requestFns.update, [TYPE_RESPONSE]: response => ({type: ENTITY_UPDATE, response}),
[TYPE_RESPONSE]: responseFns.update [TYPE_REQUEST]: request => ({type: ENTITY_UPDATE, request})
}; };
const removeFns = { const removeFns = {
[TYPE_WORKSPACE]: workspaceFns.remove, [TYPE_WORKSPACE]: workspaceFns.remove,
[TYPE_REQUEST_GROUP]: requestGroupFns.remove, [TYPE_REQUEST_GROUP]: requestGroupFns.remove,
[TYPE_REQUEST]: requestFns.remove [TYPE_RESPONSE]: response => ({type: ENTITY_UPDATE, response}),
// Response doesn't have a remove function (yet...) [TYPE_REQUEST]: request => ({type: ENTITY_REMOVE, request})
}; };
export function update (doc) { export function update (doc) {
// Using dispatch here because Redux gets mad if we don't return anything return updateFns[doc.type](doc);
// in a normal action creator
return dispatch => {
if (updateFns.hasOwnProperty(doc.type)) {
dispatch(updateFns[doc.type](doc));
}
}
} }
export function remove (doc) { export function remove (doc) {
// Using dispatch here because Redux gets mad if we don't return anything return removeFns[doc.type](doc);
// in a normal action creator
return dispatch => {
if (removeFns.hasOwnProperty(doc.type)) {
dispatch(removeFns[doc.type](doc));
}
}
} }

View File

@ -1,93 +1,35 @@
import {combineReducers} from 'redux'
import * as network from '../../lib/network' import * as network from '../../lib/network'
import {loadStart, loadStop} from './global' import {loadStart, loadStop} from './global'
import {show} from './modals' import {show} from './modals'
import {MODAL_REQUEST_RENAME} from '../../lib/constants' 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'; export const REQUEST_CHANGE_FILTER = 'requests/filter';
const initialState = {
filter: ''
};
// ~~~~~~~~ // // ~~~~~~~~ //
// REDUCERS // // REDUCERS //
// ~~~~~~~~ // // ~~~~~~~~ //
function allReducer (state = [], action) { export default function (state = initialState, 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) {
switch (action.type) { switch (action.type) {
case REQUEST_CHANGE_FILTER: case REQUEST_CHANGE_FILTER:
return action.filter; const filter = action.filter;
return Object.assign({}, state, {filter});
default: default:
return state; 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 // // ACTIONS //
// ~~~~~~~ // // ~~~~~~~ //
export function remove (request) {
return {type: REQUEST_DELETE, request};
}
export function update (request) {
return {type: REQUEST_UPDATE, request};
}
export function changeFilter (filter) { export function changeFilter (filter) {
return {type: REQUEST_CHANGE_FILTER, 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 // // REDUCERS //
// ~~~~~~~~ // // ~~~~~~~~ //
export default function (state = initialState, action) { // None yet
switch (action.type) {
case RESPONSE_UPDATE:
return Object.assign({}, state, {
[action.response.parentId]: action.response
});
default:
return state;
}
}
// ~~~~~~~ // // ~~~~~~~ //
// ACTIONS // // ACTIONS //
// ~~~~~~~ // // ~~~~~~~ //
export function update (response) { // None yet...
return {type: RESPONSE_UPDATE, response};
}